import Modal from './common/ResizableDraggableModal';
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import Editor, { type OnMount } from './MonacoEditor';
import { message, Input, Form, MenuProps, Button, Segmented } from 'antd';
import { format } from 'sql-formatter';
import { v4 as uuidv4 } from 'uuid';
import { TabData, ColumnDefinition, type SavedQuery, type SqlSnippet } from '../types';
import { type SqlLog, useStore } from '../store';
import { DBQuery, DBQueryWithCancel, DBQueryMulti, DBQueryMultiInTransaction, DBQueryMultiTransactional, DBGetTables, DBGetAllColumns, DBGetDatabases, DBGetColumns, CancelQuery, GenerateQueryID, WriteSQLFile, ExportSQLFile } from '../../wailsjs/go/app/App';
import { GONAVI_ROW_KEY } from './DataGrid';
import { EventsOn } from '../../wailsjs/runtime';
import { findConnectionMutatingStatements } from '../utils/connectionReadOnly';
import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities';
import { applyMongoQueryAutoLimit, convertMongoShellToJsonCommand } from "../utils/mongodb";
import { getShortcutDisplayLabel, getShortcutPlatform, getShortcutPrimaryModifierDisplayLabel, isEditableElement, isImeComposingKeyEvent, isShortcutMatch, comboToMonacoKeyBinding, normalizeShortcutCombo, resolveShortcutBinding } from "../utils/shortcuts";
import { useAutoFetchVisibility } from '../utils/autoFetchVisibility';
import { buildRpcConnectionConfig } from '../utils/connectionRpcConfig';
import { isPostgresSchemaDialect } from '../utils/connectionDriverType';
import { isOracleLikeDialect, resolveSqlDialect, resolveSqlFunctions, resolveSqlKeywords } from '../utils/sqlDialect';
import { applyQueryAutoLimit } from '../utils/queryAutoLimit';
import {
    buildQueryResultPageSql,
    createInitialQueryResultPagination,
    resolveQueryResultPaginationTotal,
} from '../utils/queryResultPagination';
import { extractQueryResultTableRef, type QueryResultTableRef } from '../utils/queryResultTable';
import { quoteIdentPart, quoteQualifiedIdent } from '../utils/sql';
import { formatSqlExecutionError, hasLocalizedSqlTimeoutKeyword } from '../utils/sqlErrorSemantics';
import { canReusePendingSqlEditorTransactionForType, shouldUseSqlEditorManagedTransactionForType } from '../utils/sqlEditorTransaction';
import { findSqlStatementRanges, resolveCurrentSqlStatementRange, resolveExecutableSql } from '../utils/sqlStatementSelection';
import { isMacLikePlatform } from '../utils/appearance';
import { splitSidebarQualifiedName } from '../utils/sidebarLocate';
import { buildMySQLCompatibleViewMetadataSqls, isSidebarViewTableType, normalizeSidebarViewName } from '../utils/sidebarMetadata';
import { SIDEBAR_SQL_EDITOR_DRAG_MIME, decodeSidebarSqlEditorDragPayload, hasSidebarSqlEditorDragPayload } from '../utils/sidebarSqlDrag';
import { resolveUniqueKeyGroupsFromIndexes } from './dataGridCopyInsert';
import { t as translate } from '../i18n';
import { buildSqlAnalysisWorkbenchTab } from '../utils/sqlAnalysisTab';
import { isLocalizedUntitledQueryTitle } from '../utils/queryTabTitle';
import { buildSqlServerObjectDefinitionQueries } from '../utils/sqlServerObjectDefinition';
import {
    clampQueryEditorEditorHeight,
    resolveQueryEditorEditorHeightFromRatio,
    resolveQueryEditorEditorHeightRatio,
    sanitizeQueryEditorEditorHeightRatio,
} from '../utils/queryEditorSplitLayout';
import {
    DUCKDB_ROWID_LOCATOR_COLUMN,
    ORACLE_ROWID_LOCATOR_COLUMN,
    type EditRowLocator,
} from '../utils/rowLocator';
import {
    clearQueryTabDraft,
    getQueryTabDraft,
    hasQueryTabDraft,
    persistQueryTabDraftSnapshot,
} from '../utils/sqlFileTabDrafts';
import {
    clearQueryEditorResultSession,
    saveQueryEditorResultSession,
    takeQueryEditorResultSession,
} from '../utils/queryEditorResultSessionCache';
import { buildEditableTriggerSql } from '../utils/triggerEditSql';
import {
    getColumnDefinitionComment,
    getColumnDefinitionKey,
    getColumnDefinitionName,
    getColumnDefinitionType,
} from '../utils/columnDefinition';
import QueryEditorResultsPanel, {
    QUERY_EDITOR_SQL_LOG_TAB_KEY,
    type QueryEditorResultSet,
} from './QueryEditorResultsPanel';
import ResultDiffWizard from './resultDiff/ResultDiffWizard';
import ResultDiffPanel from './resultDiff/ResultDiffPanel';
import ViewDataVerifyWizard from './resultDiff/ViewDataVerifyWizard';
import type {
  ResultDiffColumnMeta,
  ResultDiffComparableResult,
  ResultDiffSummary,
} from '../utils/resultDiff/types';
import {
  isViewEditSql,
  resolveViewNameForVerify,
} from '../utils/resultDiff/viewDataVerify';
import { SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS } from './QueryEditorTransactionSettings';
import QueryEditorTransactionToolbar from './QueryEditorTransactionToolbar';
import QueryEditorToolbar from './QueryEditorToolbar';
import { useSqlEditorTransactionController } from './useSqlEditorTransactionController';
import {
    type CompletionColumnMeta,
    type CompletionPackageMeta,
    type CompletionRoutineMeta,
    type CompletionSequenceMeta,
    type CompletionTableMeta,
    type CompletionTriggerMeta,
    type CompletionViewMeta,
    type QueryEditorNavigationTarget,
    type QueryStatementPlan,
    QUERY_EDITOR_HOVER_DELAY_MS,
    QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH,
    QUERY_EDITOR_OBJECT_DECORATION_MAX_TEXT_LENGTH,
    QUERY_EDITOR_PERSISTED_DRAFT_MAX_TEXT_LENGTH,
    QUERY_EDITOR_SQL_QUALIFIER_COMPLETION_REGEX,
    QUERY_EDITOR_SQL_TABLE_REFERENCE_REGEX,
    QUERY_EDITOR_SQL_THREE_PART_COMPLETION_REGEX,
    appendCommentToDetail,
    areSqlStatementListsEqual,
    buildCompletionDocumentation,
    buildColumnCompletionDetail,
    buildColumnCompletionDocumentation,
    buildCompletionFunctionsMetadataQuerySpecs,
    buildCompletionMaterializedViewsMetadataQuerySpecs,
    buildCompletionPackagesMetadataQuerySpecs,
    buildCompletionSequencesMetadataQuerySpecs,
    buildCompletionTableCommentSQL,
    buildCompletionTriggersMetadataQuerySpecs,
    buildCompletionViewsMetadataQuerySpecs,
    buildQueryEditorAliasMap,
    buildQueryEditorHoverMarkdown,
    buildQualifiedCompletionName,
    clearQueryEditorLinkDecorations,
    clearQueryEditorObjectDecorations,
    collectQueryEditorObjectDecorationCandidates,
    collectQueryEditorReferencedDatabaseNames,
    getCaseInsensitiveValue,
    getFirstRowValue,
    getNormalizedPositionAtOffset,
    hasQueryEditorCtrlMetaModifier,
    getInitialEditorQuery,
    getMySQLShowTablesName,
    getNormalizedOffsetAtPosition,
    getQueryEditorDecorationModelTextIfLightweight,
    getQueryEditorObjectResolveText,
    getTabQueryValue,
    isDocumentLevelShortcutTarget,
    isQueryEditorPrimaryMouseButton,
    normalizeCommentText,
    normalizeQueryResultMessages,
    normalizeCompletionQualifiedName,
    normalizeEditorPosition,
    normalizeExecutedSqlKey,
    normalizeMetadataDialect,
    queryCompletionMetadataRowsBySpecs,
    readSidebarSqlDropText,
    matchLeadingSelectTableReference,
    resolveNewQueryDefaultTemplate,
    resolveEventTargetNode,
    resolveNextResultSetIndex,
    resolveOracleExactCaseTableReference,
    resolveOracleLikeDefaultSchemaName,
    resolveOracleLikeExecutionSchemaName,
    resolveOracleLikeLookupSchemaCandidates,
    resolveQueryEditorFormatterLanguage,
    resolveQueryEditorHoverTarget,
    resolveQueryEditorNavigationDecorations,
    resolveQueryEditorNavigationTarget,
    resolveQueryLocatorPlan,
    rewriteLeadingSelectTableReference,
    splitCompletionSchemaAndTable,
    splitQueryIdentifierPathSegments,
    stripCompletionIdentifierQuotes,
} from './queryEditor/QueryEditorHelpers';
import {
    buildQueryEditorAiInlineSuggestOptions,
    getQueryEditorAiService,
    requestQueryEditorInlineCompletion,
    requestQueryEditorTextToSql,
    resolveInlineSqlGhostPreviewText,
    resolveQueryEditorInlineMemoryInsertText,
    resolveQueryEditorInlineCompletionIntentDetails,
    shouldTriggerQueryEditorInlineObjectSuggestFallback,
    shouldRequestQueryEditorInlineCompletion,
    type QueryEditorAiApplyMode,
    type QueryEditorAiContext,
    type QueryEditorAiEditorSnapshot,
} from './queryEditor/QueryEditorAiAssist';
export {
    collectQueryEditorObjectDecorationCandidates,
    resolveQueryEditorNavigationDecorations,
    resolveQueryEditorNavigationTarget,
} from './queryEditor/QueryEditorHelpers';

const buildQueryEditorMonacoActionLabel = (key: string): string =>
    `GoNavi: ${translate(key)}`;

const QUERY_EDITOR_MONACO_FIND_OPTIONS = {
    addExtraSpaceOnTop: true,
} as const;
const QUERY_EDITOR_NATIVE_SELECT_CURRENT_LINE_EVENT = 'gonavi:native-select-current-line';
const QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO = 'Meta+E';
const QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID = 'gonavi.suppressMacFindWithSelection';
const QUERY_EDITOR_AI_INLINE_DEBOUNCE_MS = 90;
const QUERY_EDITOR_AI_INLINE_CONTEXT_KEY = 'gonaviAiInlineSuggestionVisible';

const normalizeQueryEditorInlineMemorySqlKey = (sql: string): string => (
    String(sql || '')
        .replace(/\r\n?/g, '\n')
        .replace(/\s+/g, ' ')
        .trim()
        .toLowerCase()
);

const matchesQueryEditorInlineMemoryDb = (currentDb: string, candidateDb?: string): boolean => {
    const normalizedCurrentDb = String(currentDb || '').trim().toLowerCase();
    const normalizedCandidateDb = String(candidateDb || '').trim().toLowerCase();
    if (!normalizedCurrentDb || !normalizedCandidateDb) {
        return true;
    }
    return normalizedCurrentDb === normalizedCandidateDb;
};

const buildQueryEditorInlineMemoryEntries = ({
    currentConnectionId,
    currentDb,
    savedQueries,
    sqlLogs,
}: {
    currentConnectionId: string;
    currentDb: string;
    savedQueries: SavedQuery[];
    sqlLogs: SqlLog[];
}): Array<{ sql: string }> => {
    const ranked = new Map<string, { sql: string; score: number; latestAt: number }>();
    const addCandidate = (sql: string, score: number, latestAt: number) => {
        const text = String(sql || '').trim();
        if (!text) {
            return;
        }
        const key = normalizeQueryEditorInlineMemorySqlKey(text);
        if (!key) {
            return;
        }
        const existing = ranked.get(key);
        if (!existing) {
            ranked.set(key, { sql: text, score, latestAt });
            return;
        }
        existing.score += score;
        if (latestAt >= existing.latestAt) {
            existing.latestAt = latestAt;
            existing.sql = text;
        }
    };

    savedQueries.forEach((query) => {
        if (currentConnectionId && String(query.connectionId || '').trim() !== currentConnectionId) {
            return;
        }
        if (!matchesQueryEditorInlineMemoryDb(currentDb, query.dbName)) {
            return;
        }
        addCandidate(query.sql, 600, Number(query.createdAt || 0));
    });

    sqlLogs.forEach((log) => {
        if (log.status !== 'success') {
            return;
        }
        if (!matchesQueryEditorInlineMemoryDb(currentDb, log.dbName)) {
            return;
        }
        addCandidate(log.sql, 80, Number(log.timestamp || 0));
    });

    return [...ranked.values()]
        .sort((left, right) => (
            right.score - left.score
            || right.latestAt - left.latestAt
            || left.sql.length - right.sql.length
        ))
        .slice(0, 16)
        .map((entry) => ({ sql: entry.sql }));
};

const buildQueryEditorMonacoOptions = (isObjectEditQueryTab: boolean) => ({
    minimap: { enabled: false },
    automaticLayout: true,
    fixedOverflowWidgets: true,
    find: QUERY_EDITOR_MONACO_FIND_OPTIONS,
    hover: {
        enabled: true,
        delay: QUERY_EDITOR_HOVER_DELAY_MS,
        above: false,
    },
    scrollBeyondLastLine: false,
    quickSuggestions: { other: true, comments: false, strings: false },
    suggestOnTriggerCharacters: true,
    inlineSuggest: buildQueryEditorAiInlineSuggestOptions(),
    ...(isObjectEditQueryTab
        ? {
            fontSize: 14,
            lineHeight: 24,
            lineNumbersMinChars: 4,
            stickyScroll: { enabled: false },
        }
        : {}),
});

const QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER = '{SQL}';

const escapeQueryEditorObjectEditSqlLiteral = (value: unknown): string => (
    String(value || '').replace(/'/g, "''")
);

const CLIPBOARD_WRITE_TIMEOUT_MS = 2000;

const copyQueryEditorTextToClipboard = async (text: string): Promise<boolean> => {
    const tryAsyncClipboardWrite = async (): Promise<boolean> => {
        if (typeof navigator?.clipboard?.writeText !== 'function') {
            return false;
        }

        try {
            const written = await Promise.race([
                navigator.clipboard.writeText(text).then(() => true as const),
                new Promise<false>((resolve) => setTimeout(() => resolve(false), CLIPBOARD_WRITE_TIMEOUT_MS)),
            ]);
            return written;
        } catch {
            return false;
        }
    };

    if (typeof document?.createElement === 'function' && typeof document?.execCommand === 'function') {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.setAttribute('readonly', 'true');
        textarea.setAttribute('aria-hidden', 'true');
        Object.assign(textarea.style, {
            position: 'fixed',
            top: '0',
            left: '-9999px',
            opacity: '0',
            pointerEvents: 'none',
        });

        try {
            document.body?.appendChild?.(textarea);
            textarea.focus?.();
            textarea.select?.();
            textarea.setSelectionRange?.(0, text.length);
            if (document.execCommand('copy')) {
                return true;
            }
        } catch {
            // Fall through to async clipboard APIs when execCommand is unavailable.
        } finally {
            textarea.remove?.();
        }
    }

    if (await tryAsyncClipboardWrite()) {
        return true;
    }
    return false;
};

const getQueryEditorObjectEditRawValue = (row: Record<string, any>, candidateKeys: string[]): any => {
    const keyMap = new Map<string, any>();
    Object.keys(row || {}).forEach((key) => keyMap.set(key.toLowerCase(), row[key]));
    for (const key of candidateKeys) {
        if (keyMap.has(key.toLowerCase())) {
            const value = keyMap.get(key.toLowerCase());
            if (value !== undefined && value !== null) return value;
        }
    }
    return undefined;
};

const normalizeQueryEditorRoutineDefinitionForEdit = (
    definition: string,
    routineName: string,
    routineType: string,
): string => {
    const text = String(definition || '').trim();
    if (!text) return '';
    if (/^\s*create\b/i.test(text)) return text;
    if (/^\s*(function|procedure)\b/i.test(text)) {
        return `CREATE OR REPLACE ${text}`;
    }
    const normalizedType = String(routineType || 'FUNCTION').trim().toUpperCase().includes('PROC')
        ? 'PROCEDURE'
        : 'FUNCTION';
    return `CREATE OR REPLACE ${normalizedType} ${routineName}\n${text}`;
};

const buildQueryEditorRoutineEditFallbackSql = (
    routineName: string,
    routineType: string,
): string => {
    const normalizedType = String(routineType || 'FUNCTION').trim().toUpperCase().includes('PROC')
        ? 'PROCEDURE'
        : 'FUNCTION';
    if (normalizedType === 'PROCEDURE') {
        return `CREATE OR REPLACE PROCEDURE ${routineName}()\nBEGIN\n    -- TODO: edit procedure body\nEND;`;
    }
    return `CREATE OR REPLACE FUNCTION ${routineName}()\nRETURNS INTEGER\nBEGIN\n    -- TODO: edit function body\n    RETURN 0;\nEND;`;
};

const normalizeQueryEditorMySQLViewDDL = (rawDefinition: unknown): string => {
    const text = String(rawDefinition || '').trim();
    if (!text) return '';

    const normalized = text.replace(/\r\n/g, '\n').trim().replace(/;+\s*$/, '');
    const createViewPrefixPattern = /^\s*create\s+(?:algorithm\s*=\s*\w+\s+)?(?:definer\s*=\s*(?:`[^`]+`|\S+)\s*@\s*(?:`[^`]+`|\S+)\s+)?(?:sql\s+security\s+(?:definer|invoker)\s+)?view\s+/i;
    if (createViewPrefixPattern.test(normalized)) {
        return `${normalized.replace(createViewPrefixPattern, 'CREATE OR REPLACE VIEW ')};`;
    }

    if (/^\s*(select|with)\b/i.test(normalized)) {
        return normalized;
    }

    return `${normalized};`;
};

const normalizeQueryEditorSqlPlusSlashTerminator = (sql: string): string => (
    String(sql || '').trim().replace(/(^|\n)([ \t]*\/[ \t]*);+([ \t]*(?:--[^\n]*)?)\s*$/i, '$1$2$3')
);

const hasQueryEditorSqlPlusSlashTerminator = (sql: string): boolean => (
    /(?:^|\n)[ \t]*\/[ \t]*(?:--[^\n]*)?\s*$/i.test(String(sql || '').trim())
);

const ensureQueryEditorObjectEditSqlTerminator = (sql: string): string => {
    const normalized = normalizeQueryEditorSqlPlusSlashTerminator(sql);
    if (!normalized) return '';
    if (hasQueryEditorSqlPlusSlashTerminator(normalized)) return normalized;
    return /;\s*$/.test(normalized) ? normalized : `${normalized};`;
};

const isQueryEditorCommentOnlyDefinition = (definition: string): boolean => {
    const normalized = String(definition || '').replace(/\r\n/g, '\n').trim();
    if (!normalized) return false;
    const lines = normalized
        .split('\n')
        .map((line) => line.trim())
        .filter(Boolean);
    return lines.length > 0 && lines.every((line) => line.startsWith('--'));
};

const withQueryEditorCreateOrReplacePackageHeaders = (definition: string): string => {
    const normalized = String(definition || '').replace(/\r\n/g, '\n').trim();
    if (!normalized) return '';
    return normalized
        .split(/(?=^\s*PACKAGE(?:\s+BODY)?\b)/gim)
        .map((part) => part.trim())
        .filter(Boolean)
        .map((part) => (/^\s*CREATE\b/i.test(part) ? part : `CREATE OR REPLACE ${part}`))
        .join('\n/\n');
};

const buildQueryEditorQualifiedObjectName = (objectName: string, schemaName?: string): string => {
    const normalizedObjectName = String(objectName || '').trim();
    const normalizedSchemaName = String(schemaName || '').trim();
    if (!normalizedObjectName || !normalizedSchemaName || normalizedObjectName.includes('.')) {
        return normalizedObjectName;
    }
    return `${normalizedSchemaName}.${normalizedObjectName}`;
};

const buildQueryEditorEditableDefinitionSql = (
    objectType: 'view-def' | 'sequence-def' | 'package-def',
    definition: string,
    objectName: string,
    objectLabel: string,
): string => {
    const normalizedDefinition = String(definition || '').trim();
    const header = [
        `-- ${translate('definition_viewer.edit.comment_title', { object: objectLabel, name: objectName })}`,
        `-- ${translate('definition_viewer.edit.comment_compatibility')}`,
    ].join('\n') + '\n';
    if (!normalizedDefinition) {
        return `${header}-- ${translate('definition_viewer.edit.comment_empty_definition', { name: objectName })}\n`;
    }

    if (isQueryEditorCommentOnlyDefinition(normalizedDefinition)) {
        return `${header}${ensureQueryEditorObjectEditSqlTerminator(normalizedDefinition)}`;
    }

    if (objectType === 'view-def' && !/^\s*create\b/i.test(normalizedDefinition)) {
        if (/^\s*view\b/i.test(normalizedDefinition)) {
            return `${header}${ensureQueryEditorObjectEditSqlTerminator(normalizedDefinition.replace(/^\s*view\b/i, 'CREATE OR REPLACE VIEW'))}`;
        }
        return `${header}CREATE OR REPLACE VIEW ${objectName} AS\n${ensureQueryEditorObjectEditSqlTerminator(normalizedDefinition)}`;
    }

    if (objectType === 'sequence-def' && !/^\s*create\b/i.test(normalizedDefinition)) {
        return `${header}${ensureQueryEditorObjectEditSqlTerminator(`CREATE SEQUENCE ${objectName}\n${normalizedDefinition}`)}`;
    }

    if (
        objectType === 'package-def'
        && !/^\s*create\b/i.test(normalizedDefinition)
        && /^\s*package\b/i.test(normalizedDefinition)
    ) {
        return `${header}${withQueryEditorCreateOrReplacePackageHeaders(normalizedDefinition)}`;
    }

    return `${header}${ensureQueryEditorObjectEditSqlTerminator(normalizedDefinition)}`;
};

const buildQueryEditorObjectDefinitionConnectionConfig = (conn: any): Record<string, any> => ({
    ...conn.config,
    port: Number(conn.config?.port),
    password: conn.config?.password || '',
    database: conn.config?.database || '',
    useSSH: conn.config?.useSSH || false,
    ssh: conn.config?.ssh || { host: '', port: 22, user: '', password: '', keyPath: '' },
});

const runQueryEditorObjectDefinitionCandidates = async (
    config: Record<string, any>,
    dbName: string,
    queries: string[],
    collectAll = false,
): Promise<any[]> => {
    const collectedRows: any[] = [];
    for (const query of queries) {
        const sql = String(query || '').trim();
        if (!sql || sql.startsWith('--')) continue;
        try {
            const result = await DBQuery(buildRpcConnectionConfig(config) as any, dbName, sql);
            if (!result.success || !Array.isArray(result.data) || result.data.length === 0) {
                continue;
            }
            if (!collectAll) {
                return result.data;
            }
            collectedRows.push(...result.data);
        } catch {
            // 元数据定义读取失败时保留编辑模板。
        }
    }
    return collectedRows;
};

const buildQueryEditorViewDefinitionQueries = (
    dialect: string,
    viewName: string,
    dbName: string,
    schemaName?: string,
    viewKind?: 'view' | 'materialized',
): string[] => {
    const parsed = splitSidebarQualifiedName(viewName);
    const objectName = parsed.objectName || viewName;
    const schema = String(schemaName || parsed.schemaName || '').trim();
    const safeName = escapeQueryEditorObjectEditSqlLiteral(objectName);
    const safeDbName = escapeQueryEditorObjectEditSqlLiteral(dbName);

    switch (dialect) {
        case 'mysql':
        case 'starrocks': {
            const viewRef = schema
                ? `\`${schema.replace(/`/g, '``')}\`.\`${objectName.replace(/`/g, '``')}\``
                : `\`${objectName.replace(/`/g, '``')}\``;
            if (dialect === 'starrocks' && viewKind === 'materialized') {
                return [
                    `SHOW CREATE MATERIALIZED VIEW ${viewRef}`,
                    `SHOW CREATE TABLE ${viewRef}`,
                ];
            }
            return [
                `SHOW CREATE VIEW ${viewRef}`,
                safeDbName
                    ? `SELECT VIEW_DEFINITION AS view_definition FROM information_schema.views WHERE table_schema = '${safeDbName}' AND table_name = '${safeName}' LIMIT 1`
                    : '',
                `SHOW CREATE TABLE ${viewRef}`,
            ].filter(Boolean);
        }
        case 'postgres':
        case 'kingbase':
        case 'highgo':
        case 'vastbase':
        case 'opengauss':
        case 'gaussdb': {
            const schemaRef = schema || 'public';
            return [`SELECT pg_get_viewdef('${escapeQueryEditorObjectEditSqlLiteral(schemaRef)}.${safeName}'::regclass, true) AS view_definition`];
        }
        case 'sqlserver':
            return buildSqlServerObjectDefinitionQueries('view', viewName, dbName, 'view_definition');
        case 'oracle': {
            const owner = schema ? escapeQueryEditorObjectEditSqlLiteral(schema).toUpperCase() : (safeDbName ? safeDbName.toUpperCase() : '');
            if (owner) {
                return [`SELECT TEXT AS view_definition FROM ALL_VIEWS WHERE OWNER = '${owner}' AND VIEW_NAME = '${safeName.toUpperCase()}'`];
            }
            return [`SELECT TEXT AS view_definition FROM USER_VIEWS WHERE VIEW_NAME = '${safeName.toUpperCase()}'`];
        }
        case 'sqlite':
            return [`SELECT sql AS view_definition FROM sqlite_master WHERE type='view' AND name='${safeName}'`];
        case 'duckdb': {
            const schemaRef = schema || 'main';
            return [`SELECT view_definition FROM information_schema.views WHERE table_schema = '${escapeQueryEditorObjectEditSqlLiteral(schemaRef)}' AND table_name = '${safeName}' LIMIT 1`];
        }
        default:
            return [];
    }
};

const extractQueryEditorViewDefinition = (dialect: string, data: any[]): string => {
    if (!Array.isArray(data) || data.length === 0) return '';
    const row = data[0] as Record<string, any>;
    if (dialect === 'mysql' || dialect === 'starrocks') {
        const direct = getQueryEditorObjectEditRawValue(row, ['view_definition', 'VIEW_DEFINITION']);
        if (direct !== undefined && direct !== null && String(direct).trim()) {
            return normalizeQueryEditorMySQLViewDDL(direct);
        }
        const sqlKey = Object.keys(row).find((key) => {
            const lowerKey = key.toLowerCase();
            return lowerKey.includes('create view') || lowerKey === 'create view' || lowerKey.includes('create table');
        });
        if (sqlKey) {
            return normalizeQueryEditorMySQLViewDDL(row[sqlKey]);
        }
        const createValue = Object.values(row).find((value) => {
            const text = String(value || '').toUpperCase();
            return text.includes('CREATE') && (text.includes('VIEW') || text.includes('TABLE'));
        });
        return createValue ? normalizeQueryEditorMySQLViewDDL(createValue) : '';
    }
    if (dialect === 'sqlserver') {
        const direct = getQueryEditorObjectEditRawValue(row, ['view_definition', 'definition']);
        if (direct !== undefined && direct !== null && String(direct).trim()) {
            return String(direct);
        }
        return data
            .map((item) => getQueryEditorObjectEditRawValue(item, ['Text', 'text']))
            .filter((value) => value !== undefined && value !== null)
            .map((value) => String(value))
            .join('');
    }
    const direct = getQueryEditorObjectEditRawValue(row, ['view_definition', 'definition', 'sql', 'text', 'TEXT', 'SQL']);
    return direct !== undefined && direct !== null ? String(direct) : String(Object.values(row)[0] || '');
};

const buildQueryEditorSequenceDefinitionQueries = (
    dialect: string,
    sequenceName: string,
    dbName: string,
    schemaName?: string,
): string[] => {
    const parsed = splitSidebarQualifiedName(sequenceName);
    const objectName = parsed.objectName || sequenceName;
    const schema = String(schemaName || parsed.schemaName || '').trim();
    const safeName = escapeQueryEditorObjectEditSqlLiteral(objectName);
    const safeDbName = escapeQueryEditorObjectEditSqlLiteral(dbName);
    const owner = schema ? escapeQueryEditorObjectEditSqlLiteral(schema).toUpperCase() : (safeDbName ? safeDbName.toUpperCase() : '');

    switch (dialect) {
        case 'oracle':
            if (owner) {
                return [`SELECT SEQUENCE_OWNER, SEQUENCE_NAME, MIN_VALUE, MAX_VALUE, INCREMENT_BY, CYCLE_FLAG, ORDER_FLAG, CACHE_SIZE, LAST_NUMBER FROM ALL_SEQUENCES WHERE SEQUENCE_OWNER = '${owner}' AND SEQUENCE_NAME = '${safeName.toUpperCase()}'`];
            }
            return [`SELECT SEQUENCE_NAME, MIN_VALUE, MAX_VALUE, INCREMENT_BY, CYCLE_FLAG, ORDER_FLAG, CACHE_SIZE, LAST_NUMBER FROM USER_SEQUENCES WHERE SEQUENCE_NAME = '${safeName.toUpperCase()}'`];
        case 'postgres':
        case 'kingbase':
        case 'highgo':
        case 'vastbase':
        case 'opengauss':
        case 'gaussdb': {
            const schemaRef = schema || 'public';
            return [`SELECT sequence_schema, sequence_name, data_type, start_value, minimum_value, maximum_value, increment FROM information_schema.sequences WHERE sequence_schema = '${escapeQueryEditorObjectEditSqlLiteral(schemaRef)}' AND sequence_name = '${safeName}' LIMIT 1`];
        }
        default:
            return [];
    }
};

const buildQueryEditorSequenceDefinitionFromRow = (
    row: Record<string, any>,
    fallbackSequenceName: string,
    fallbackSchemaName?: string,
): string => {
    const sequenceName = String(getQueryEditorObjectEditRawValue(row, ['sequence_name']) || splitSidebarQualifiedName(fallbackSequenceName).objectName || fallbackSequenceName).trim();
    const owner = String(getQueryEditorObjectEditRawValue(row, ['sequence_owner', 'owner', 'sequence_schema']) || fallbackSchemaName || splitSidebarQualifiedName(fallbackSequenceName).schemaName || '').trim();
    const name = buildQueryEditorQualifiedObjectName(sequenceName, owner);
    if (!name) return '';

    const clauses: string[] = [];
    const increment = getQueryEditorObjectEditRawValue(row, ['increment_by', 'increment']);
    const minValue = getQueryEditorObjectEditRawValue(row, ['min_value', 'minimum_value']);
    const maxValue = getQueryEditorObjectEditRawValue(row, ['max_value', 'maximum_value']);
    const cacheSize = Number(getQueryEditorObjectEditRawValue(row, ['cache_size']));
    const cycleFlag = String(getQueryEditorObjectEditRawValue(row, ['cycle_flag']) || '').trim().toUpperCase();
    const orderFlag = String(getQueryEditorObjectEditRawValue(row, ['order_flag']) || '').trim().toUpperCase();

    if (increment !== undefined && increment !== null && String(increment).trim() !== '') {
        clauses.push(`INCREMENT BY ${increment}`);
    }
    if (minValue !== undefined && minValue !== null && String(minValue).trim() !== '') {
        clauses.push(`MINVALUE ${minValue}`);
    }
    if (maxValue !== undefined && maxValue !== null && String(maxValue).trim() !== '') {
        clauses.push(`MAXVALUE ${maxValue}`);
    }
    if (Number.isFinite(cacheSize)) {
        clauses.push(cacheSize > 0 ? `CACHE ${cacheSize}` : 'NOCACHE');
    }
    if (cycleFlag) clauses.push(cycleFlag === 'Y' ? 'CYCLE' : 'NOCYCLE');
    if (orderFlag) clauses.push(orderFlag === 'Y' ? 'ORDER' : 'NOORDER');

    return [`CREATE SEQUENCE ${name}`, ...clauses.map((clause) => `  ${clause}`)].join('\n');
};

const extractQueryEditorSequenceDefinition = (
    data: any[],
    sequenceName: string,
    schemaName?: string,
): string => {
    if (!Array.isArray(data) || data.length === 0) return '';
    return buildQueryEditorSequenceDefinitionFromRow(data[0] as Record<string, any>, sequenceName, schemaName);
};

const buildQueryEditorPackageDefinitionQueries = (
    dialect: string,
    packageName: string,
    dbName: string,
    schemaName?: string,
): string[] => {
    const parsed = splitSidebarQualifiedName(packageName);
    const objectName = parsed.objectName || packageName;
    const schema = String(schemaName || parsed.schemaName || '').trim();
    const safeName = escapeQueryEditorObjectEditSqlLiteral(objectName);
    const safeDbName = escapeQueryEditorObjectEditSqlLiteral(dbName);

    if (dialect !== 'oracle') {
        return [];
    }

    const owner = schema ? escapeQueryEditorObjectEditSqlLiteral(schema).toUpperCase() : (safeDbName ? safeDbName.toUpperCase() : '');
    if (owner) {
        return [
            `SELECT TEXT FROM ALL_SOURCE WHERE OWNER = '${owner}' AND NAME = '${safeName.toUpperCase()}' AND TYPE = 'PACKAGE' ORDER BY LINE`,
            `SELECT TEXT FROM ALL_SOURCE WHERE OWNER = '${owner}' AND NAME = '${safeName.toUpperCase()}' AND TYPE = 'PACKAGE BODY' ORDER BY LINE`,
        ];
    }
    return [
        `SELECT TEXT FROM USER_SOURCE WHERE NAME = '${safeName.toUpperCase()}' AND TYPE = 'PACKAGE' ORDER BY LINE`,
        `SELECT TEXT FROM USER_SOURCE WHERE NAME = '${safeName.toUpperCase()}' AND TYPE = 'PACKAGE BODY' ORDER BY LINE`,
    ];
};

const extractQueryEditorPackageDefinition = (data: any[]): string => {
    if (!Array.isArray(data) || data.length === 0) return '';
    return data
        .map((row: any) => getQueryEditorObjectEditRawValue(row, ['text', 'TEXT']) ?? Object.values(row || {})[0] ?? '')
        .map((value) => String(value))
        .join('');
};

const buildQueryEditorTriggerDefinitionQueries = (
    dialect: string,
    triggerName: string,
    dbName: string,
    schemaName?: string,
): string[] => {
    const parsed = splitSidebarQualifiedName(triggerName);
    const objectName = parsed.objectName || triggerName;
    const schema = String(schemaName || parsed.schemaName || '').trim();
    const safeName = escapeQueryEditorObjectEditSqlLiteral(objectName);
    const safeDbName = escapeQueryEditorObjectEditSqlLiteral(dbName);

    switch (dialect) {
        case 'mysql':
        case 'starrocks': {
            const triggerRef = schema
                ? `\`${schema.replace(/`/g, '``')}\`.\`${objectName.replace(/`/g, '``')}\``
                : `\`${objectName.replace(/`/g, '``')}\``;
            return [
                `SHOW CREATE TRIGGER ${triggerRef}`,
                safeDbName
                    ? `SELECT TRIGGER_NAME, TRIGGER_SCHEMA, EVENT_OBJECT_SCHEMA, EVENT_OBJECT_TABLE, ACTION_TIMING, EVENT_MANIPULATION, ACTION_ORIENTATION, ACTION_STATEMENT FROM information_schema.triggers WHERE trigger_schema = '${safeDbName}' AND trigger_name = '${safeName}' LIMIT 1`
                    : '',
            ].filter(Boolean);
        }
        case 'postgres':
        case 'kingbase':
        case 'highgo':
        case 'vastbase':
        case 'opengauss':
        case 'gaussdb':
            return [`SELECT pg_get_triggerdef(t.oid, true) AS trigger_definition
FROM pg_trigger t
JOIN pg_class c ON t.tgrelid = c.oid
WHERE t.tgname = '${safeName}'
  AND NOT t.tgisinternal
LIMIT 1`];
        case 'sqlserver':
            return buildSqlServerObjectDefinitionQueries('trigger', triggerName, dbName, 'trigger_definition');
        case 'oracle': {
            if (schema) {
                return [
                    `SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeName.toUpperCase()}', '${escapeQueryEditorObjectEditSqlLiteral(schema).toUpperCase()}') AS trigger_definition FROM DUAL`,
                    `SELECT TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${escapeQueryEditorObjectEditSqlLiteral(schema).toUpperCase()}' AND TRIGGER_NAME = '${safeName.toUpperCase()}'`,
                ];
            }
            if (safeDbName) {
                return [
                    `SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeName.toUpperCase()}', '${safeDbName.toUpperCase()}') AS trigger_definition FROM DUAL`,
                    `SELECT TRIGGER_BODY FROM ALL_TRIGGERS WHERE OWNER = '${safeDbName.toUpperCase()}' AND TRIGGER_NAME = '${safeName.toUpperCase()}'`,
                ];
            }
            return [
                `SELECT DBMS_METADATA.GET_DDL('TRIGGER', '${safeName.toUpperCase()}') AS trigger_definition FROM DUAL`,
                `SELECT TRIGGER_BODY FROM USER_TRIGGERS WHERE TRIGGER_NAME = '${safeName.toUpperCase()}'`,
            ];
        }
        case 'sqlite':
            return [`SELECT sql AS trigger_definition FROM sqlite_master WHERE type = 'trigger' AND name = '${safeName}'`];
        default:
            return [];
    }
};

const extractQueryEditorTriggerDefinition = (dialect: string, data: any[]): string => {
    if (!Array.isArray(data) || data.length === 0) return '';
    const row = data[0] as Record<string, any>;
    const direct = getQueryEditorObjectEditRawValue(row, ['trigger_definition', 'definition', 'sql', 'SQL']);
    if (direct !== undefined && direct !== null && String(direct).trim()) {
        return String(direct);
    }
    if (dialect === 'mysql' || dialect === 'starrocks') {
        const statementKey = Object.keys(row).find((key) => {
            const lowerKey = key.toLowerCase();
            return lowerKey.includes('statement') || lowerKey.includes('create trigger');
        });
        if (statementKey) return String(row[statementKey] || '');
        const createValue = Object.values(row).find((value) => String(value || '').toUpperCase().includes('CREATE TRIGGER'));
        return createValue ? String(createValue) : String(getQueryEditorObjectEditRawValue(row, ['ACTION_STATEMENT', 'action_statement']) || '');
    }
    return String(getQueryEditorObjectEditRawValue(row, ['TRIGGER_BODY', 'trigger_body', 'TEXT', 'text']) || Object.values(row)[0] || '');
};

const buildQueryEditorAiContextPrompt = (connection: any, database: string): string => {
    if (!connection) {
        return '';
    }

    const sourceLabel = String(connection.config?.type || '').trim() || translate('query_editor.ai_prompt.default_source');
    const databaseLabel = String(database || '').trim() || translate('query_editor.ai_prompt.default_database');

    return translate('query_editor.ai_prompt.context', {
        type: sourceLabel,
        name: `"${connection.name}"`,
        database: `"${databaseLabel}"`,
    });
};

const resolveQueryEditorAiConnectionHost = (connection: any): string => {
    const config = connection?.config || {};
    if (Array.isArray(config.hosts)) {
        const hosts = config.hosts
            .map((item: any) => String(typeof item === 'string' ? item : item?.host || item?.hostname || item?.address || '').trim())
            .filter(Boolean);
        if (hosts.length > 0) {
            return hosts.join(', ');
        }
    }
    return String(config.host || config.hostname || config.server || config.address || '').trim();
};

// HMR 重载时释放旧注册避免补全和 hover 内容重复
const _g = globalThis as any;
const SQL_COMPLETION_PROVIDER_VERSION = '20260702-ai-inline-sql-v1';
if (!_g.__gonaviSqlCompletionState) {
    _g.__gonaviSqlCompletionState = { registered: false, version: '', disposables: [] as any[] };
}
if (!Array.isArray(_g.__gonaviSqlCompletionState.disposables)) {
    _g.__gonaviSqlCompletionState.disposables = [];
}
let sqlCompletionRegistered = _g.__gonaviSqlCompletionState.registered;
let sqlCompletionDisposables = _g.__gonaviSqlCompletionState.disposables;

// 模块级共享变量：completion provider 从这些变量读取当前活跃 Tab 的状态。
// 每个 QueryEditor 实例在成为活跃 Tab 时更新这些变量，确保 provider 始终使用正确的上下文。
let sharedCurrentDb = '';
let sharedCurrentConnectionId = '';
let sharedConnections: any[] = [];
let sharedTablesData: CompletionTableMeta[] = [];
let sharedAllColumnsData: CompletionColumnMeta[] = [];

// AI 补全的元数据预热可能把整库列（数十万条）灌入 sharedAllColumnsData，普通补全逐列全量
// 扫描会阻塞主线程；按 (库, 表名末段) 建索引，并以数组身份为键缓存，数组重新赋值时自动失效。
const sharedColumnsIndexCache = new WeakMap<CompletionColumnMeta[], Map<string, CompletionColumnMeta[]>>();
const findSharedPreloadedColumns = (dbName: string, tableName: string): CompletionColumnMeta[] => {
    const columns = sharedAllColumnsData;
    let index = sharedColumnsIndexCache.get(columns);
    if (!index) {
        index = new Map<string, CompletionColumnMeta[]>();
        columns.forEach((column) => {
            const dbLower = String(column.dbName || '').toLowerCase();
            const tableLower = String(column.tableName || '').toLowerCase();
            const lastPartLower = String(splitCompletionSchemaAndTable(column.tableName || '').table || '').toLowerCase();
            const keys = lastPartLower && lastPartLower !== tableLower
                ? [`${dbLower}\u0000${tableLower}`, `${dbLower}\u0000${lastPartLower}`]
                : [`${dbLower}\u0000${tableLower}`];
            keys.forEach((key) => {
                const list = index!.get(key);
                if (list) {
                    list.push(column);
                } else {
                    index!.set(key, [column]);
                }
            });
        });
        sharedColumnsIndexCache.set(columns, index);
    }
    const key = `${String(dbName || '').toLowerCase()}\u0000${String(tableName || '').toLowerCase()}`;
    return index.get(key) || [];
};

// 普通建议的“相关列”按 SQL 中引用的表标识符（db.table / table / 纯表名）匹配，
// 同样避免对全量列做逐条正则扫描；索引以列数组身份为键缓存。
const sharedColumnsByIdentCache = new WeakMap<CompletionColumnMeta[], Map<string, CompletionColumnMeta[]>>();
const collectSharedColumnsForTableIdents = (
    columns: CompletionColumnMeta[],
    idents: ReadonlySet<string>,
): CompletionColumnMeta[] => {
    let index = sharedColumnsByIdentCache.get(columns);
    if (!index) {
        index = new Map<string, CompletionColumnMeta[]>();
        columns.forEach((column) => {
            const tableLower = String(column.tableName || '').toLowerCase();
            const fullLower = `${String(column.dbName || '').toLowerCase()}.${tableLower}`;
            const pureLower = String(splitCompletionSchemaAndTable(column.tableName || '').table || '').toLowerCase();
            new Set([fullLower, tableLower, pureLower]).forEach((key) => {
                if (!key) {
                    return;
                }
                const list = index!.get(key);
                if (list) {
                    list.push(column);
                } else {
                    index!.set(key, [column]);
                }
            });
        });
        sharedColumnsByIdentCache.set(columns, index);
    }
    const seen = new Set<CompletionColumnMeta>();
    const result: CompletionColumnMeta[] = [];
    idents.forEach((ident) => {
        (index!.get(ident) || []).forEach((column) => {
            if (seen.has(column)) {
                return;
            }
            seen.add(column);
            result.push(column);
        });
    });
    return result;
};
let sharedVisibleDbs: string[] = [];
let sharedViewsData: CompletionViewMeta[] = [];
let sharedMaterializedViewsData: CompletionViewMeta[] = [];
let sharedTriggersData: CompletionTriggerMeta[] = [];
let sharedRoutinesData: CompletionRoutineMeta[] = [];
let sharedSequencesData: CompletionSequenceMeta[] = [];
let sharedPackagesData: CompletionPackageMeta[] = [];
let sharedColumnsCacheData: Record<string, any[]> = {};
let sharedActiveEditorModelUri = '';
const sharedLazyTablesCache: Record<string, CompletionTableMeta[] | undefined> = {};
const sharedLazyTablesInFlight: Record<string, Promise<CompletionTableMeta[]> | undefined> = {};
const createEmptySqlCompletionResult = () => ({ suggestions: [] as any[] });
const isSqlCompletionRequestCancelled = (token?: { isCancellationRequested?: boolean } | null) =>
    Boolean(token?.isCancellationRequested);
const clearRecord = (record: Record<string, unknown>) => {
    Object.keys(record).forEach((key) => {
        delete record[key];
    });
};
const QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT = 260;

const getCompletionTableNameFromRow = (row: any): string => (
    normalizeCommentText(
        getCaseInsensitiveValue(row, ['table_name', 'TABLE_NAME', 'Table', 'table', 'name', 'Name'])
            ?? Object.values(row || {})[0],
    )
);

const getCompletionTableCommentFromRow = (row: any): string => (
    normalizeCommentText(getCaseInsensitiveValue(row, [
        'table_comment',
        'TABLE_COMMENT',
        'comment',
        'comments',
        'Comment',
        'COMMENTS',
        'description',
        'Description',
    ]))
);

const getCompletionTableComment = (
    tableComments: Map<string, string>,
    tableName: string,
    rowComment = '',
): string => {
    const parsed = splitCompletionSchemaAndTable(String(tableName || ''));
    return tableComments.get(String(tableName || '').toLowerCase())
        || (parsed.table ? tableComments.get(parsed.table.toLowerCase()) : '')
        || normalizeCommentText(rowComment);
};

const buildCompletionTableMeta = (
    dbName: string,
    row: any,
    tableComments: Map<string, string>,
): CompletionTableMeta | null => {
    const tableName = getCompletionTableNameFromRow(row);
    if (!tableName) return null;
    return {
        dbName,
        tableName,
        comment: getCompletionTableComment(tableComments, tableName, getCompletionTableCommentFromRow(row)) || undefined,
    };
};

const fetchCompletionTableCommentMap = async (
    config: any,
    dbName: string,
    metadataDialect: string,
): Promise<Map<string, string>> => {
    const tableComments = new Map<string, string>();
    const tableCommentSQL = buildCompletionTableCommentSQL(metadataDialect, dbName);
    if (!tableCommentSQL) return tableComments;

    try {
        const resTableComments = await DBQuery(buildRpcConnectionConfig(config) as any, dbName, tableCommentSQL);
        if (resTableComments.success && Array.isArray(resTableComments.data)) {
            resTableComments.data.forEach((row: any) => {
                const tableName = normalizeCommentText(getCaseInsensitiveValue(row, ['table_name', 'TABLE_NAME', 'name', 'Name']));
                if (!tableName) return;
                tableComments.set(tableName.toLowerCase(), getCompletionTableCommentFromRow(row));
            });
        }
    } catch {
        // 表备注只是补全增强，失败时保留原有表名补全。
    }
    return tableComments;
};

const buildSqlSnippetVariableMap = (now: Date): Record<string, string> => {
    const pad = (value: number) => String(value).padStart(2, '0');
    return {
        CURRENT_YEAR: String(now.getFullYear()),
        CURRENT_MONTH: pad(now.getMonth() + 1),
        CURRENT_DATE: pad(now.getDate()),
        CURRENT_HOUR: pad(now.getHours()),
        CURRENT_MINUTE: pad(now.getMinutes()),
        CURRENT_SECOND: pad(now.getSeconds()),
        CURRENT_SECONDS_UNIX: String(Math.floor(now.getTime() / 1000)),
        UUID: uuidv4(),
        RANDOM: String(Math.floor(100000 + Math.random() * 900000)),
    };
};

const materializeSqlSnippetText = (body: string): string => {
    const tabstopValues = new Map<string, string>();
    const variableMap = buildSqlSnippetVariableMap(new Date());
    return String(body || '')
        .replace(/\$\{(\d+)\|([^}]+)\|\}/g, (_match, index: string, rawChoices: string) => {
            const choice = String(rawChoices || '')
                .split(',')
                .map((item) => item.trim())
                .find(Boolean) || '';
            if (index !== '0') {
                tabstopValues.set(index, choice);
            }
            return choice;
        })
        .replace(/\$\{([A-Z_]+)\}/g, (match, variableName: string) => (
            Object.prototype.hasOwnProperty.call(variableMap, variableName)
                ? variableMap[variableName]
                : match
        ))
        .replace(/\$\{(\d+):([^}]+)\}/g, (_match, index: string, placeholder: string) => {
            const value = String(placeholder || '');
            if (index !== '0') {
                tabstopValues.set(index, value);
            }
            return value;
        })
        .replace(/\$(\d+)/g, (_match, index: string) => (
            index === '0' ? '' : (tabstopValues.get(index) ?? '')
        ));
};

const resetSharedQueryEditorMetadata = () => {
    sharedCurrentDb = '';
    sharedTablesData = [];
    sharedAllColumnsData = [];
    sharedVisibleDbs = [];
    sharedViewsData = [];
    sharedMaterializedViewsData = [];
    sharedTriggersData = [];
    sharedRoutinesData = [];
    sharedSequencesData = [];
    sharedPackagesData = [];
    sharedColumnsCacheData = {};
    sharedActiveEditorModelUri = '';
    clearRecord(sharedLazyTablesCache);
    clearRecord(sharedLazyTablesInFlight);
};


const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isActive = true }) => {
  const appearance = useStore(state => state.appearance);
  const [query, setQuery] = useState(() => getInitialEditorQuery(
      tab,
      resolveNewQueryDefaultTemplate(appearance.newQuerySqlTemplate),
  ));
  const isExternalSQLFileTab = Boolean(String(tab.filePath || '').trim());
  const isObjectEditQueryTab = tab.type === 'query' && tab.queryMode === 'object-edit';
  const queryEditorMonacoOptions = useMemo(
      () => buildQueryEditorMonacoOptions(isObjectEditQueryTab),
      [isObjectEditQueryTab],
  );
  
  type ResultSet = QueryEditorResultSet;

  // Result Sets (session cache survives detach/attach remounts)
  const restoredResultSessionRef = useRef(takeQueryEditorResultSession(tab.id));
  const [resultSets, setResultSets] = useState<ResultSet[]>(
    () => restoredResultSessionRef.current?.resultSets || [],
  );
  const [activeResultKey, setActiveResultKey] = useState<string>(
    () => restoredResultSessionRef.current?.activeResultKey || '',
  );
  const resultSetsRef = useRef(resultSets);
  const activeResultKeyRef = useRef(activeResultKey);
  resultSetsRef.current = resultSets;
  activeResultKeyRef.current = activeResultKey;
  const [loading, setLoading] = useState(false);
  const [executionError, setExecutionError] = useState<string>('');
  const [, setCurrentQueryId] = useState<string>('');
  const [isSqlSnippetPickerOpen, setIsSqlSnippetPickerOpen] = useState(false);
  const [sqlSnippetPickerKeyword, setSqlSnippetPickerKeyword] = useState('');
  const runSeqRef = useRef(0);
  const currentQueryIdRef = useRef('');
  const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
  const [saveModalMode, setSaveModalMode] = useState<'save' | 'rename'>('save');
  const [saveForm] = Form.useForm();

  // Database Selection
  const [currentConnectionId, setCurrentConnectionId] = useState<string>(tab.connectionId);
  const [currentDb, setCurrentDb] = useState<string>(tab.dbName || '');
  const [dbList, setDbList] = useState<string[]>([]);
  const [isTextToSqlModalOpen, setIsTextToSqlModalOpen] = useState(false);
  const [textToSqlInstruction, setTextToSqlInstruction] = useState('');
  const [textToSqlApplyMode, setTextToSqlApplyMode] = useState<QueryEditorAiApplyMode>('insert');
  const [textToSqlGenerating, setTextToSqlGenerating] = useState(false);
  const [resultDiffWizardOpen, setResultDiffWizardOpen] = useState(false);
  const [resultDiffAnchorKey, setResultDiffAnchorKey] = useState<string>('');
  const [resultDiffSession, setResultDiffSession] = useState<{
    jobId: string;
    summary: ResultDiffSummary;
    leftLabel: string;
    rightLabel: string;
    columnMeta?: Record<string, ResultDiffColumnMeta>;
  } | null>(null);
  const [viewDataVerifyOpen, setViewDataVerifyOpen] = useState(false);

  // Resizing state
  const [editorHeight, setEditorHeight] = useState(300);
  const editorStageRef = useRef<HTMLDivElement | null>(null);
  const editorShellRef = useRef<HTMLDivElement | null>(null);
  const editorRef = useRef<any>(null);
  const monacoRef = useRef<any>(null);
  const runQueryActionRef = useRef<any>(null);
  const selectCurrentStatementActionRef = useRef<any>(null);
  const macFindWithSelectionGuardActionRef = useRef<any>(null);
  const duplicateCurrentLineActionRef = useRef<any>(null);
  const saveQueryActionRef = useRef<any>(null);
  const findInEditorActionRef = useRef<any>(null);
  const formatSqlActionRef = useRef<any>(null);
  const triggerSqlAiCompletionActionRef = useRef<any>(null);
  const triggerSqlAiCompletionKeydownDisposableRef = useRef<any>(null);
  const insertSqlSnippetActionRef = useRef<any>(null);
  const aiContextMenuActionDisposablesRef = useRef<any[]>([]);
  const toggleQueryResultsPanelActionRef = useRef<any>(null);
  const lastExternalQueryRef = useRef<string>(getTabQueryValue(tab));
  const lastLocalQueryRef = useRef<string>(query);
  const imeCompositionFallbackRef = useRef<{
      editor: any;
      valueBefore: string;
      selectionBefore: any;
      positionBefore: { lineNumber: number; column: number } | null;
      committedText: string;
  } | null>(null);
  const imeCompositionFallbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const lastEditorCursorPositionRef = useRef<any>(null);
  const lastHoverTargetPositionRef = useRef<{ lineNumber: number; column: number } | null>(null);
  const lastExecutedEditorQueryRef = useRef<string>('');
  const linkDecorationIdsRef = useRef<string[]>([]);
  const ctrlMetaPressedRef = useRef(false);
  const objectDecorationIdsRef = useRef<string[]>([]);
  const aiInlineGhostDecorationIdsRef = useRef<string[]>([]);
  const aiInlineGhostOverlayRef = useRef<HTMLSpanElement | null>(null);
  const aiInlineGhostVisibleContextKeyRef = useRef<any>(null);
  const aiInlineGhostTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const aiInlineGhostRequestSeqRef = useRef(0);
  const triggerAiInlineCompletionRef = useRef<(() => void) | null>(null);
  const aiContextMetadataWarmupRef = useRef<Record<string, Promise<boolean> | undefined>>({});
  const aiContextCacheRef = useRef<{ deps: unknown[]; value: QueryEditorAiContext } | null>(null);
  const triggerSqlAiCompletionAltPressedRef = useRef(false);
  const triggerSqlAiCompletionAltGestureAtRef = useRef(0);
  const triggerSqlAiCompletionFallbackRef = useRef<{ observedAt: number } | null>(null);
  const triggerSqlAiCompletionFallbackApplyingRef = useRef(false);
  const aiInlineGhostRef = useRef<{
      insertText: string;
      modelUri: string;
      position: { lineNumber: number; column: number };
      snapshot: QueryEditorAiEditorSnapshot;
  } | null>(null);
  const aiInlineGhostAcceptingRef = useRef(false);
  const objectHoverActionRef = useRef<any>(null);
  const dragRef = useRef<{ startY: number, startHeight: number, currentHeight: number } | null>(null);
  const pendingEditorHeightRef = useRef(editorHeight);
  const resizeFrameRef = useRef<number | null>(null);
  const queryEditorRootRef = useRef<HTMLDivElement | null>(null);
  const editorPaneRef = useRef<HTMLDivElement | null>(null);
  const tablesRef = useRef<CompletionTableMeta[]>([]); // Store tables for autocomplete (cross-db)
  const allColumnsRef = useRef<CompletionColumnMeta[]>([]); // Store all columns (cross-db)
  const viewsRef = useRef<CompletionViewMeta[]>([]);
  const materializedViewsRef = useRef<CompletionViewMeta[]>([]);
  const triggersRef = useRef<CompletionTriggerMeta[]>([]);
  const routinesRef = useRef<CompletionRoutineMeta[]>([]);
  const sequencesRef = useRef<CompletionSequenceMeta[]>([]);
  const packagesRef = useRef<CompletionPackageMeta[]>([]);
  const visibleDbsRef = useRef<string[]>([]); // Store visible databases for cross-db intellisense
  const metadataFetchKeyRef = useRef<string>('');
  const metadataContextKeyRef = useRef<string>('');
  /** SQL 中引用到的库集合变化时触发跨库元数据补拉（供超链接/补全） */
  const [sqlReferencedMetadataKey, setSqlReferencedMetadataKey] = useState('');
  const sqlReferencedMetadataTimerRef = useRef<number | null>(null);
  const lastSqlReferencedMetadataKeyRef = useRef('');

  const connections = useStore(state => state.connections);
  const queryCapableConnections = useMemo(
      () => connections.filter(c => getDataSourceCapabilities(c.config).supportsQueryEditor),
      [connections]
  );

  const addSqlLog = useStore(state => state.addSqlLog);
  const sqlLogCount = useStore(state => state.sqlLogs.length);
  const sqlLogs = useStore(state => state.sqlLogs);
  const addTab = useStore(state => state.addTab);
  const setActiveContext = useStore(state => state.setActiveContext);
  const updateQueryTabDraft = useStore(state => state.updateQueryTabDraft);
  const activeTabId = useStore(state => state.activeTabId);
  const savedQueries = useStore(state => state.savedQueries);
  const sqlSnippets = useStore(state => state.sqlSnippets);
  const currentConnectionIdRef = useRef(currentConnectionId);
  const currentDbRef = useRef(currentDb);
  const inlineSqlMemoryEntries = useMemo(() => buildQueryEditorInlineMemoryEntries({
      currentConnectionId,
      currentDb,
      savedQueries,
      sqlLogs,
  }), [currentConnectionId, currentDb, savedQueries, sqlLogs]);
  const draftSnapshotTab = useMemo(() => ({
      id: tab.id,
      title: tab.title,
      connectionId: tab.connectionId,
      dbName: tab.dbName,
      filePath: tab.filePath,
      savedQueryId: tab.savedQueryId,
      readOnly: tab.readOnly,
  }), [tab.connectionId, tab.dbName, tab.filePath, tab.id, tab.readOnly, tab.savedQueryId, tab.title]);
  const connectionsRef = useRef(connections);
  const columnsCacheRef = useRef<Record<string, ColumnDefinition[]>>({});
  const saveQuery = useStore(state => state.saveQuery);
  const theme = useStore(state => state.theme);
  const languagePreference = useStore((state) => state.languagePreference);
  void languagePreference;
  const darkMode = theme === 'dark';
  const isV2Ui = appearance.uiVersion === 'v2';
  const sqlFormatOptions = useStore(state => state.sqlFormatOptions);
  const setSqlFormatOptions = useStore(state => state.setSqlFormatOptions);
  const queryOptions = useStore(state => state.queryOptions);
  const setQueryOptions = useStore(state => state.setQueryOptions);
  const queryEditorEditorHeightRatio = sanitizeQueryEditorEditorHeightRatio(
      queryOptions?.queryEditorEditorHeightRatio,
  );
  const sqlEditorTransactionOptions = useStore(state => state.sqlEditorTransactionOptions);
  const setSqlEditorTransactionOptions = useStore(state => state.setSqlEditorTransactionOptions);
  const [isResultPanelVisible, setIsResultPanelVisible] = useState(
      () => restoredResultSessionRef.current?.isResultPanelVisible
          ?? (tab.resultPanelVisible === true)
  );
  const isResultPanelVisibleRef = useRef(isResultPanelVisible);
  isResultPanelVisibleRef.current = isResultPanelVisible;

  useEffect(() => {
      // Keep result panel state across detach/attach remounts of the same tab.
      return () => {
          saveQueryEditorResultSession(tab.id, {
              resultSets: resultSetsRef.current,
              activeResultKey: activeResultKeyRef.current,
              isResultPanelVisible: isResultPanelVisibleRef.current,
          });
      };
  }, [tab.id]);
  const shortcutOptions = useStore(state => state.shortcutOptions);
  const activeShortcutPlatform = getShortcutPlatform(isMacLikePlatform());
  const runQueryShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'runQuery', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  // SQL 诊断 / 慢 SQL 历史的快捷键绑定（从 store 读取，用户可在快捷键管理面板自定义）
  const diagnoseQueryShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'diagnoseQuery', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const showSlowQueriesShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'showSlowQueries', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const sortedSqlSnippets = useMemo(
      () => [...sqlSnippets].sort((left, right) => (
          left.prefix.localeCompare(right.prefix) || left.name.localeCompare(right.name)
      )),
      [sqlSnippets],
  );
  const filteredSqlSnippets = useMemo(() => {
      const keyword = String(sqlSnippetPickerKeyword || '').trim().toLowerCase();
      if (!keyword) {
          return sortedSqlSnippets;
      }
      return sortedSqlSnippets.filter((snippet) => (
          [
              snippet.prefix,
              snippet.name,
              snippet.description,
              snippet.syntaxHelp,
              snippet.body,
          ].some((field) => String(field || '').toLowerCase().includes(keyword))
      ));
  }, [sortedSqlSnippets, sqlSnippetPickerKeyword]);
  const sqlSnippetPickerEmptyLabel = useMemo(
      () => (
          String(sqlSnippetPickerKeyword || '').trim()
              ? translate('query_editor.snippet_picker.empty_filtered')
              : translate('query_editor.snippet_picker.empty')
      ),
      [sqlSnippetPickerKeyword],
  );

  const openSqlAnalysisWorkbench = useCallback(
      (view: 'diagnose' | 'slow-query', nextSql?: string) => {
          const connectionId = String(currentConnectionId || '').trim();
          if (!connectionId) {
              message.warning(translate('query_editor.message.connection_not_found'));
              return;
          }
          const dbName = String(currentDb || tab.dbName || '').trim();
          addTab(buildSqlAnalysisWorkbenchTab({
              connectionId,
              dbName: dbName || undefined,
              query: typeof nextSql === 'string' && nextSql.trim() ? nextSql : undefined,
              view,
          }));
      },
      [addTab, currentConnectionId, currentDb, tab.dbName],
  );

  const handleCloseSqlSnippetPicker = useCallback(() => {
      setIsSqlSnippetPickerOpen(false);
      setSqlSnippetPickerKeyword('');
  }, []);

  const handleOpenSqlSnippetPicker = useCallback(() => {
      setSqlSnippetPickerKeyword('');
      setIsSqlSnippetPickerOpen(true);
  }, []);

  const handleOpenSnippetSettingsFromPicker = useCallback(() => {
      handleCloseSqlSnippetPicker();
      window.dispatchEvent(new CustomEvent('gonavi:open-snippet-settings'));
  }, [handleCloseSqlSnippetPicker]);

  const registerInsertSqlSnippetContextMenuAction = useCallback((editor: any) => {
      if (insertSqlSnippetActionRef.current) {
          insertSqlSnippetActionRef.current.dispose();
          insertSqlSnippetActionRef.current = null;
      }
      if (!editor) {
          return;
      }

      insertSqlSnippetActionRef.current = editor.addAction({
          id: 'gonavi.insertSqlSnippet',
          label: translate('query_editor.action.insert_sql_snippet'),
          contextMenuGroupId: '8_snippet',
          contextMenuOrder: 1,
          run: handleOpenSqlSnippetPicker,
      });
  }, [handleOpenSqlSnippetPicker]);

  // SQL 诊断 / 慢 SQL 历史的快捷键监听（必须在 binding 声明之后）
  useEffect(() => {
    if (!isActive) return;
    const handler = (e: KeyboardEvent) => {
      if (diagnoseQueryShortcutBinding?.enabled && isShortcutMatch(e, diagnoseQueryShortcutBinding.combo)) {
        e.preventDefault();
        openSqlAnalysisWorkbench('diagnose', getCurrentQuery());
        return;
      }
      if (showSlowQueriesShortcutBinding?.enabled && isShortcutMatch(e, showSlowQueriesShortcutBinding.combo)) {
        e.preventDefault();
        openSqlAnalysisWorkbench('slow-query');
      }
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [diagnoseQueryShortcutBinding, isActive, openSqlAnalysisWorkbench, showSlowQueriesShortcutBinding]);
  const selectCurrentStatementShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'selectCurrentStatement', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const duplicateCurrentLineShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'duplicateCurrentLine', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const saveQueryShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'saveQuery', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const formatSqlShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'formatSql', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const triggerSqlAiCompletionShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'triggerSqlAiCompletion', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const toggleQueryResultsPanelShortcutBinding = useMemo(
      () => resolveShortcutBinding(shortcutOptions, 'toggleQueryResultsPanel', activeShortcutPlatform),
      [activeShortcutPlatform, shortcutOptions],
  );
  const findInEditorShortcutCombo = useMemo(
      () => activeShortcutPlatform === 'mac' ? 'Meta+F' : 'Ctrl+F',
      [activeShortcutPlatform],
  );
  const primaryShortcutModifierLabel = useMemo(
      () => getShortcutPrimaryModifierDisplayLabel(activeShortcutPlatform),
      [activeShortcutPlatform],
  );
  const isTriggerSqlAiCompletionShortcutEvent = useCallback((event: any): boolean => {
      const binding = triggerSqlAiCompletionShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return false;
      }
      if (isShortcutMatch(event, binding.combo)) {
          return true;
      }
      if (normalizeShortcutCombo(binding.combo) !== 'Alt+\\') {
          return false;
      }

      const key = String(
          event?.key
          || event?.nativeEvent?.key
          || event?.browserEvent?.key
          || '',
      ).trim();
      const code = String(
          event?.code
          || event?.nativeEvent?.code
          || event?.browserEvent?.code
          || '',
      ).trim();
      const keyCode = Number(
          event?.keyCode
          ?? event?.which
          ?? event?.nativeEvent?.keyCode
          ?? event?.nativeEvent?.which
          ?? event?.browserEvent?.keyCode
          ?? event?.browserEvent?.which
          ?? 0,
      );
      const isBackslashKey = key === '\\'
          || code === 'Backslash'
          || code === 'IntlBackslash'
          || keyCode === 220
          || keyCode === 226;
      return isBackslashKey && triggerSqlAiCompletionAltPressedRef.current;
  }, [triggerSqlAiCompletionShortcutBinding]);
  const isPossibleTriggerSqlAiCompletionFallbackEvent = useCallback((event: any): boolean => {
      const binding = triggerSqlAiCompletionShortcutBinding;
      if (!binding?.enabled || normalizeShortcutCombo(binding.combo) !== 'Alt+\\') {
          return false;
      }

      const key = String(
          event?.key
          || event?.nativeEvent?.key
          || event?.browserEvent?.key
          || '',
      ).trim();
      const code = String(
          event?.code
          || event?.nativeEvent?.code
          || event?.browserEvent?.code
          || '',
      ).trim();
      const keyCode = Number(
          event?.keyCode
          ?? event?.which
          ?? event?.nativeEvent?.keyCode
          ?? event?.nativeEvent?.which
          ?? event?.browserEvent?.keyCode
          ?? event?.browserEvent?.which
          ?? 0,
      );
      const isLikelyBackslashKey = key === '\\'
          || key === 'Process'
          || code === 'Backslash'
          || code === 'IntlBackslash'
          || keyCode === 220
          || keyCode === 226;
      const hasAltIntent = Boolean(
          event?.altKey
          || event?.nativeEvent?.altKey
          || event?.browserEvent?.altKey
          || triggerSqlAiCompletionAltPressedRef.current
      );
      return isLikelyBackslashKey && hasAltIntent;
  }, [triggerSqlAiCompletionShortcutBinding]);
  const registerTriggerSqlAiCompletionAction = useCallback((editor: any, monaco: any) => {
      if (triggerSqlAiCompletionActionRef.current) {
          triggerSqlAiCompletionActionRef.current.dispose();
          triggerSqlAiCompletionActionRef.current = null;
      }
      if (!editor || !monaco) {
          return;
      }

      const binding = triggerSqlAiCompletionShortcutBinding;
      const keyBinding = binding?.enabled && binding.combo
          ? comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode)
          : null;
      triggerSqlAiCompletionActionRef.current = editor.addAction({
          id: 'gonavi.triggerSqlAiCompletion',
          label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.triggerSqlAiCompletion.label'),
          keybindings: keyBinding ? [keyBinding.keyMod | keyBinding.keyCode] : [],
          contextMenuGroupId: '7_ai',
          contextMenuOrder: 0,
          run: () => {
              triggerAiInlineCompletionRef.current?.();
          },
      });
  }, [triggerSqlAiCompletionShortcutBinding]);
  useEffect(() => {
      // Prefer remount session cache (detach/attach); otherwise follow tab draft flag.
      if (restoredResultSessionRef.current && restoredResultSessionRef.current.isResultPanelVisible !== undefined) {
          setIsResultPanelVisible(restoredResultSessionRef.current.isResultPanelVisible === true);
          return;
      }
      setIsResultPanelVisible(tab.resultPanelVisible === true);
  }, [tab.id, tab.resultPanelVisible]);
  const updateResultPanelVisibility = useCallback((visible: boolean) => {
      setIsResultPanelVisible(visible);
      updateQueryTabDraft(tab.id, { resultPanelVisible: visible });
  }, [tab.id, updateQueryTabDraft]);
  const toggleResultPanelVisibility = useCallback(() => {
      setIsResultPanelVisible((previousVisible) => {
          const nextVisible = !previousVisible;
          updateQueryTabDraft(tab.id, { resultPanelVisible: nextVisible });
          return nextVisible;
      });
  }, [tab.id, updateQueryTabDraft]);
  const handleOpenEditorFind = useCallback(() => {
      const editor = editorRef.current;
      if (!editor) {
          return;
      }

      editor.focus?.();
      try {
          const findAction = editor.getAction?.('actions.find');
          if (findAction?.run) {
              void findAction.run();
              return;
          }
      } catch {
          // Fall back to Monaco's built-in command id if the action lookup fails.
      }

      editor.trigger?.('keyboard', 'actions.find', null);
  }, []);
  const handleShowSqlExecutionLog = useCallback((mode: 'open' | 'toggle' = 'toggle') => {
      if (!isActive) {
          return;
      }
      if (mode !== 'open' && isResultPanelVisible && activeResultKey === QUERY_EDITOR_SQL_LOG_TAB_KEY) {
          updateResultPanelVisibility(false);
          return;
      }
      updateResultPanelVisibility(true);
      setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY);
  }, [activeResultKey, isActive, isResultPanelVisible, updateResultPanelVisibility]);
  const sqlEditorCommitMode = sqlEditorTransactionOptions?.commitMode === 'auto' ? 'auto' : 'manual';
  const sqlEditorAutoCommitDelayMs = SQL_EDITOR_AUTO_COMMIT_DELAY_OPTIONS.some((item) => item.value === sqlEditorTransactionOptions?.autoCommitDelayMs)
      ? Number(sqlEditorTransactionOptions?.autoCommitDelayMs)
      : 0;
  const {
      activatePendingSqlTransaction,
      autoCommitRemainingSeconds: sqlEditorAutoCommitRemainingSeconds,
      finishPendingSqlTransaction,
      pendingSqlTransaction,
      pendingSqlTransactionRef,
  } = useSqlEditorTransactionController({
      tabId: tab.id,
      translate: (key, params) => translate(key, params),
  });
  const autoFetchVisible = useAutoFetchVisibility();

  useEffect(() => {
      const nextContextKey = [
          String(currentConnectionId || '').trim(),
          String(currentDb || '').trim().toLowerCase(),
      ].join('\u0000');
      if (metadataContextKeyRef.current === nextContextKey) {
          return;
      }
      metadataContextKeyRef.current = nextContextKey;
      metadataFetchKeyRef.current = '';
      tablesRef.current = [];
      allColumnsRef.current = [];
      viewsRef.current = [];
      materializedViewsRef.current = [];
      triggersRef.current = [];
      routinesRef.current = [];
      columnsCacheRef.current = {};
      if (isActive) {
          resetSharedQueryEditorMetadata();
      }
  }, [currentConnectionId, currentDb, isActive]);

  const currentSavedQuery = useMemo(() => {
      const savedId = String(tab.savedQueryId || '').trim();
      if (savedId) {
          return savedQueries.find((item) => item.id === savedId) || null;
      }
      const tabId = String(tab.id || '').trim();
      if (!tabId) {
          return null;
      }
      return savedQueries.find((item) => item.id === tabId) || null;
  }, [savedQueries, tab.id, tab.savedQueryId]);

  const syncQueryDraft = useCallback((nextQuery: string) => {
      const next = String(nextQuery ?? '');
      lastLocalQueryRef.current = next;
      persistQueryTabDraftSnapshot(draftSnapshotTab, next, {
          connectionId: currentConnectionIdRef.current,
          dbName: currentDbRef.current,
      });
  }, [draftSnapshotTab]);

  const applyQueryState = useCallback((nextQuery: string) => {
      const next = String(nextQuery ?? '');
      syncQueryDraft(next);
      if (!isExternalSQLFileTab || next.length <= QUERY_EDITOR_PERSISTED_DRAFT_MAX_TEXT_LENGTH) {
          setQuery(next);
      }
  }, [isExternalSQLFileTab, syncQueryDraft]);

  const handleInsertSqlSnippet = useCallback((snippet: SqlSnippet) => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor) {
          return;
      }

      const snippetController = editor.getContribution?.('snippetController2');
      if (snippetController && typeof snippetController.insert === 'function') {
          editor.focus?.();
          snippetController.insert(snippet.body);
          const nextValue = editor.getValue?.();
          if (typeof nextValue === 'string') {
              applyQueryState(nextValue);
          }
          handleCloseSqlSnippetPicker();
          editor.focus?.();
          return;
      }

      const model = editor.getModel?.();
      if (!model || !monaco?.Range) {
          return;
      }

      const selection = editor.getSelection?.();
      const position = editor.getPosition?.()
          || { lineNumber: model.getLineCount?.() || 1, column: model.getLineMaxColumn?.(model.getLineCount?.() || 1) || 1 };
      const hasSelection = selection
          && (
              selection.startLineNumber !== selection.endLineNumber
              || selection.startColumn !== selection.endColumn
          );
      const range = hasSelection
          ? selection
          : new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column);
      const startOffset = model.getOffsetAt?.({
          lineNumber: range.startLineNumber,
          column: range.startColumn,
      });
      const plainText = materializeSqlSnippetText(snippet.body);

      editor.pushUndoStop?.();
      editor.executeEdits?.('gonavi-insert-sql-snippet', [{
          range,
          text: plainText,
          forceMoveMarkers: true,
      }]);
      editor.pushUndoStop?.();

      if (Number.isFinite(Number(startOffset)) && typeof model.getPositionAt === 'function') {
          const nextPosition = model.getPositionAt(Number(startOffset) + plainText.length);
          editor.setPosition?.(nextPosition);
          editor.setSelection?.(new monaco.Range(
              nextPosition.lineNumber,
              nextPosition.column,
              nextPosition.lineNumber,
              nextPosition.column,
          ));
      }

      const nextValue = editor.getValue?.();
      if (typeof nextValue === 'string') {
          applyQueryState(nextValue);
      }
      handleCloseSqlSnippetPicker();
      editor.focus?.();
  }, [applyQueryState, handleCloseSqlSnippetPicker]);

  useEffect(() => {
      persistQueryTabDraftSnapshot(draftSnapshotTab, query, {
          connectionId: currentConnectionIdRef.current || currentConnectionId,
          dbName: currentDbRef.current || currentDb,
      });
  }, [currentConnectionId, currentDb, draftSnapshotTab, query]);

  useEffect(() => {
      currentConnectionIdRef.current = currentConnectionId;
  }, [currentConnectionId]);

  useEffect(() => {
      if (!queryCapableConnections.some(c => c.id === currentConnectionId)) {
          const fallback = queryCapableConnections[0]?.id || '';
          if (fallback && fallback !== currentConnectionId) {
              setCurrentConnectionId(fallback);
              setCurrentDb('');
          }
      }
  }, [queryCapableConnections, currentConnectionId]);

  useEffect(() => {
      currentDbRef.current = currentDb;
  }, [currentDb]);

  useEffect(() => {
      const nextConnectionId = String(tab.connectionId || '').trim();
      const nextDb = String(tab.dbName || '').trim();
      if (nextConnectionId !== currentConnectionIdRef.current) {
          currentConnectionIdRef.current = nextConnectionId;
          setCurrentConnectionId(nextConnectionId);
      }
      if (nextDb !== currentDbRef.current) {
          currentDbRef.current = nextDb;
          setCurrentDb(nextDb);
      }
  }, [tab.id, tab.connectionId, tab.dbName]);

  useEffect(() => {
      if (isExternalSQLFileTab) return;
      const currentDraft = getQueryTabDraft(tab.id, query);
      const shouldPersistQuery = currentDraft.length <= QUERY_EDITOR_PERSISTED_DRAFT_MAX_TEXT_LENGTH;
      updateQueryTabDraft(tab.id, {
          ...(shouldPersistQuery ? { query: currentDraft } : {}),
          connectionId: currentConnectionId,
          dbName: currentDb,
      });
  }, [currentConnectionId, currentDb, isExternalSQLFileTab, query, tab.id, updateQueryTabDraft]);

  useEffect(() => {
      if (!isExternalSQLFileTab) return;
      updateQueryTabDraft(tab.id, {
          connectionId: currentConnectionId,
          dbName: currentDb,
      });
  }, [currentConnectionId, currentDb, isExternalSQLFileTab, tab.id, updateQueryTabDraft]);

  const getCurrentQuery = useCallback(() => {
      const val = editorRef.current?.getValue?.();
      if (typeof val === 'string') return val;
      return query || '';
  }, [query]);

  const buildQueryEditorAiEditorSnapshot = useCallback((): QueryEditorAiEditorSnapshot => {
      const editor = editorRef.current;
      const model = editor?.getModel?.();
      const position = normalizeEditorPosition(editor?.getPosition?.());
      const value = String(model?.getValue?.() ?? getCurrentQuery() ?? '');
      if (!model || !position || typeof model.getOffsetAt !== 'function') {
          return {
              prefix: value,
              suffix: '',
              currentLineBeforeCursor: value.split(/\r?\n/).pop() || '',
              currentLineAfterCursor: '',
          };
      }

      const offset = Number(model.getOffsetAt(position));
      const safeOffset = Number.isFinite(offset)
          ? Math.max(0, Math.min(offset, value.length))
          : value.length;
      const lineContent = String(model.getLineContent?.(position.lineNumber) || '');
      const lineColumnIndex = Math.max(0, Math.min(position.column - 1, lineContent.length));
      return {
          prefix: value.slice(0, safeOffset),
          suffix: value.slice(safeOffset),
          currentLineBeforeCursor: lineContent.slice(0, lineColumnIndex),
          currentLineAfterCursor: lineContent.slice(lineColumnIndex),
      };
  }, [getCurrentQuery]);

  const buildQueryEditorAiContext = useCallback((): QueryEditorAiContext => {
      const resolvedConnectionId = String(
          currentConnectionIdRef.current
          || currentConnectionId
          || tab.connectionId
          || '',
      ).trim();
      const conn = connectionsRef.current.find(c => c.id === resolvedConnectionId);
      const currentDbName = String(
          currentDbRef.current
          || currentDb
          || tab.dbName
          || '',
      ).trim();
      const lazyTablesEntry = sharedLazyTablesCache[`${resolvedConnectionId}|${currentDbName}`];

      // 大库下全量合并可达数十万条且每次补全请求都会调用；依赖引用未变时复用上次结果，
      // 同时保持 tables/columns 数组身份稳定，让下游按数组身份缓存的索引也能跨请求复用。
      const cacheDeps: unknown[] = [
          resolvedConnectionId,
          conn,
          currentDbName,
          lazyTablesEntry,
          sharedTablesData,
          tablesRef.current,
          sharedAllColumnsData,
          allColumnsRef.current,
          visibleDbsRef.current,
      ];
      const cached = aiContextCacheRef.current;
      if (cached && cached.deps.every((dep, index) => dep === cacheDeps[index])) {
          return cached.value;
      }

      const lazyTables = lazyTablesEntry || [];
      const mergedTablesByKey = new Map<string, CompletionTableMeta>();
      [...sharedTablesData, ...tablesRef.current, ...lazyTables].forEach((table) => {
          const tableKey = `${String(table?.dbName || '').trim().toLowerCase()}\u0000${String(table?.tableName || '').trim().toLowerCase()}`;
          if (!tableKey.trim()) {
              return;
          }
          mergedTablesByKey.set(tableKey, table);
      });
      const mergedColumnsByKey = new Map<string, CompletionColumnMeta>();
      [...sharedAllColumnsData, ...allColumnsRef.current].forEach((column) => {
          const columnKey = `${String(column?.dbName || '').trim().toLowerCase()}\u0000${String(column?.tableName || '').trim().toLowerCase()}\u0000${String(column?.name || '').trim().toLowerCase()}`;
          if (!columnKey.trim()) {
              return;
          }
          mergedColumnsByKey.set(columnKey, column);
      });
      const value: QueryEditorAiContext = {
          connectionName: conn?.name,
          host: resolveQueryEditorAiConnectionHost(conn),
          port: conn?.config?.port,
          sourceType: conn?.config?.type,
          currentDb: currentDbName,
          visibleDbs: visibleDbsRef.current,
          tables: [...mergedTablesByKey.values()],
          columns: [...mergedColumnsByKey.values()],
      };
      aiContextCacheRef.current = { deps: cacheDeps, value };
      return value;
  }, [currentConnectionId, currentDb, tab.connectionId, tab.dbName]);

  const ensureQueryEditorAiContextMetadata = useCallback(async (
      editorSnapshot: QueryEditorAiEditorSnapshot,
  ): Promise<void> => {
      const connectionId = String(
          currentConnectionIdRef.current
          || currentConnectionId
          || tab.connectionId
          || '',
      ).trim();
      const dbName = String(
          currentDbRef.current
          || currentDb
          || tab.dbName
          || '',
      ).trim();
      if (!connectionId || !dbName) {
          return;
      }

      const intent = resolveQueryEditorInlineCompletionIntentDetails(editorSnapshot);
      const normalizedDbName = dbName.toLowerCase();
      const needsTables = intent.intent === 'table_name'
          || !tablesRef.current.some((table) => String(table.dbName || '').trim().toLowerCase() === normalizedDbName);
      const needsColumns = intent.intent === 'column_name'
          && !allColumnsRef.current.some((column) => String(column.dbName || '').trim().toLowerCase() === normalizedDbName);
      if (!needsTables && !needsColumns) {
          return;
      }

      const warmupKey = `${connectionId}\u0000${normalizedDbName}\u0000${needsTables ? 'tables' : ''}\u0000${needsColumns ? 'columns' : ''}`;
      const existingWarmup = aiContextMetadataWarmupRef.current[warmupKey];
      if (existingWarmup) {
          await existingWarmup;
          return;
      }

      const warmupPromise = (async (): Promise<boolean> => {
          const conn = connectionsRef.current.find((item) => item.id === connectionId);
          if (!conn) {
              return false;
          }
          let warmupSucceeded = true;

          const config = {
              ...conn.config,
              port: Number(conn.config.port),
              password: conn.config.password || '',
              database: conn.config.database || '',
              useSSH: conn.config.useSSH || false,
              ssh: conn.config.ssh || { host: '', port: 22, user: '', password: '', keyPath: '' },
          };

          if (needsTables) {
              try {
                  const metadataDialect = normalizeMetadataDialect(conn);
                  const [tableComments, resTables] = await Promise.all([
                      fetchCompletionTableCommentMap(config, dbName, metadataDialect).catch(() => new Map<string, string>()),
                      DBGetTables(buildRpcConnectionConfig(config) as any, dbName),
                  ]);
                  if (!resTables?.success) {
                      warmupSucceeded = false;
                  }
                  if (resTables?.success && Array.isArray(resTables.data)) {
                      const fetchedTables = resTables.data
                          .map((row: any) => buildCompletionTableMeta(dbName, row, tableComments))
                          .filter((table): table is CompletionTableMeta => !!table);
                      if (fetchedTables.length > 0) {
                          const nextTableByKey = new Map(
                              tablesRef.current.map((table) => [
                                  `${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim().toLowerCase()}`,
                                  table,
                              ]),
                          );
                          fetchedTables.forEach((table) => {
                              nextTableByKey.set(
                                  `${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim().toLowerCase()}`,
                                  table,
                              );
                          });
                          tablesRef.current = [...nextTableByKey.values()];
                          sharedTablesData = tablesRef.current;
                          sharedLazyTablesCache[`${connectionId}|${dbName}`] = fetchedTables;
                      }
                  }
              } catch (error) {
                  warmupSucceeded = false;
                  console.warn('GoNavi AI inline table metadata warmup failed', error);
              }
          }

          if (needsColumns) {
              try {
                  const resCols = await DBGetAllColumns(buildRpcConnectionConfig(config) as any, dbName);
                  if (!resCols?.success) {
                      warmupSucceeded = false;
                  }
                  if (resCols?.success && Array.isArray(resCols.data)) {
                      const fetchedColumns = resCols.data.map((col: any) => ({
                          dbName,
                          tableName: col.tableName,
                          name: col.name,
                          type: col.type,
                          comment: normalizeCommentText(col.comment ?? col.Comment ?? col.COLUMN_COMMENT ?? col.column_comment ?? ''),
                      }));
                      if (fetchedColumns.length > 0) {
                          const nextColumnByKey = new Map(
                              allColumnsRef.current.map((column) => [
                                  `${String(column.dbName || '').trim().toLowerCase()}\u0000${String(column.tableName || '').trim().toLowerCase()}\u0000${String(column.name || '').trim().toLowerCase()}`,
                                  column,
                              ]),
                          );
                          fetchedColumns.forEach((column) => {
                              nextColumnByKey.set(
                                  `${String(column.dbName || '').trim().toLowerCase()}\u0000${String(column.tableName || '').trim().toLowerCase()}\u0000${String(column.name || '').trim().toLowerCase()}`,
                                  column,
                              );
                          });
                          allColumnsRef.current = [...nextColumnByKey.values()];
                          sharedAllColumnsData = allColumnsRef.current;
                      }
                  }
              } catch (error) {
                  warmupSucceeded = false;
                  console.warn('GoNavi AI inline column metadata warmup failed', error);
              }
          }
          return warmupSucceeded;
      })();

      // 成功的 warmup 结果整个会话内复用，避免每次内联补全都真实查库；失败时删除缓存以便重试。
      aiContextMetadataWarmupRef.current[warmupKey] = warmupPromise;
      let warmupSucceeded = false;
      try {
          warmupSucceeded = await warmupPromise;
      } finally {
          if (!warmupSucceeded) {
              delete aiContextMetadataWarmupRef.current[warmupKey];
          }
      }
  }, [currentConnectionId, currentDb, tab.connectionId, tab.dbName]);

  useEffect(() => {
      if (!isExternalSQLFileTab) return;
      persistQueryTabDraftSnapshot(draftSnapshotTab, getCurrentQuery(), {
          connectionId: currentConnectionIdRef.current,
          dbName: currentDbRef.current,
      });
      return () => {
          persistQueryTabDraftSnapshot(draftSnapshotTab, getCurrentQuery(), {
              connectionId: currentConnectionIdRef.current,
              dbName: currentDbRef.current,
          });
      };
  }, [draftSnapshotTab, getCurrentQuery, isExternalSQLFileTab]);

  // 当此 Tab 成为活跃 Tab 时，将本实例的状态同步到模块级共享变量
  // 确保 completion provider 始终使用当前活跃 Tab 的上下文
  useEffect(() => {
      if (!isActive) return;
      sharedCurrentDb = currentDb;
      sharedCurrentConnectionId = currentConnectionId;
      sharedConnections = connections;
      sharedTablesData = tablesRef.current;
      sharedAllColumnsData = allColumnsRef.current;
      sharedVisibleDbs = visibleDbsRef.current;
      sharedViewsData = viewsRef.current;
      sharedMaterializedViewsData = materializedViewsRef.current;
      sharedTriggersData = triggersRef.current;
      sharedRoutinesData = routinesRef.current;
      sharedSequencesData = sequencesRef.current;
      sharedPackagesData = packagesRef.current;
      sharedColumnsCacheData = columnsCacheRef.current;
      sharedActiveEditorModelUri = String(editorRef.current?.getModel?.()?.uri?.toString?.() || '');
  }, [isActive, currentDb, currentConnectionId, connections]);

  useEffect(() => {
      connectionsRef.current = connections;
  }, [connections]);

  const refreshObjectDecorations = useCallback((maxTextLength = QUERY_EDITOR_OBJECT_DECORATION_MAX_TEXT_LENGTH) => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const model = editor?.getModel?.();
      if (!editor || !monaco || !model) {
          return;
      }

      if (isObjectEditQueryTab) {
          objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, []);
          return;
      }

      const text = getQueryEditorDecorationModelTextIfLightweight(model, maxTextLength);
      if (text === null) {
          objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, []);
          return;
      }

      const decorations: any[] = [];
      const seen = new Set<string>();
      const candidates = collectQueryEditorObjectDecorationCandidates(text);

      for (const candidate of candidates) {
          const hoverTarget = resolveQueryEditorHoverTarget(
              text,
              candidate.lineContent,
              candidate.positionColumn,
              currentDbRef.current,
              visibleDbsRef.current,
              tablesRef.current,
              allColumnsRef.current,
              viewsRef.current,
              materializedViewsRef.current,
              triggersRef.current,
              routinesRef.current,
              sequencesRef.current,
              packagesRef.current,
          );
          if (!hoverTarget) continue;

          const inlineClassName = hoverTarget.kind === 'column'
              ? 'gonavi-query-editor-column-token'
              : hoverTarget.kind === 'database'
                  ? 'gonavi-query-editor-db-token'
                  : 'gonavi-query-editor-object-token';
          const key = `${candidate.lineNumber}:${hoverTarget.range.startColumn}:${hoverTarget.range.endColumn}:${inlineClassName}`;
          if (seen.has(key)) continue;
          seen.add(key);
          decorations.push({
              range: new monaco.Range(
                  candidate.lineNumber,
                  hoverTarget.range.startColumn,
                  candidate.lineNumber,
                  hoverTarget.range.endColumn,
              ),
              options: { inlineClassName },
          });
      }

      objectDecorationIdsRef.current = editor.deltaDecorations(objectDecorationIdsRef.current, decorations);
  }, [isObjectEditQueryTab]);

  const showObjectInfoAtPosition = useCallback((position?: { lineNumber: number; column: number } | null) => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const model = editor?.getModel?.();
      const normalizedPosition = normalizeEditorPosition(position || editor?.getPosition?.());
      if (!editor || !model || !normalizedPosition) {
          return false;
      }
      const lineContent = String(model.getLineContent?.(normalizedPosition.lineNumber) || '');
      const resolveText = getQueryEditorObjectResolveText(model, lineContent);
      const hoverTarget = resolveQueryEditorHoverTarget(
          resolveText,
          lineContent,
          normalizedPosition.column,
          currentDbRef.current,
          visibleDbsRef.current,
          tablesRef.current,
          allColumnsRef.current,
          viewsRef.current,
          materializedViewsRef.current,
          triggersRef.current,
          routinesRef.current,
          sequencesRef.current,
          packagesRef.current,
      );
      if (!hoverTarget) {
          return false;
      }
      editor.focus?.();
      const hoverRange = monaco
          ? new monaco.Range(
              normalizedPosition.lineNumber,
              hoverTarget.range.startColumn,
              normalizedPosition.lineNumber,
              hoverTarget.range.endColumn,
          )
          : {
              startLineNumber: normalizedPosition.lineNumber,
              startColumn: hoverTarget.range.startColumn,
              endLineNumber: normalizedPosition.lineNumber,
              endColumn: hoverTarget.range.endColumn,
          };
      const contentHoverController = editor.getContribution?.('editor.contrib.contentHover');
      if (contentHoverController?.showContentHover) {
          contentHoverController.showContentHover(hoverRange, 1, 2, false);
          return true;
      }
      editor.setPosition?.({
          lineNumber: normalizedPosition.lineNumber,
          column: hoverTarget.range.startColumn,
      });
      editor.trigger?.('gonavi-hover', 'editor.action.showHover', null);
      return true;
  }, []);

  const registerShowObjectInfoAction = useCallback(() => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) {
          return;
      }

      objectHoverActionRef.current?.dispose?.();
      const showObjectInfoKeybinding = monaco.KeyMod?.CtrlCmd && monaco.KeyCode?.KeyQ
          ? [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyQ]
          : undefined;
      objectHoverActionRef.current = editor.addAction({
          id: 'gonavi.queryEditor.showObjectInfo',
          label: buildQueryEditorMonacoActionLabel('query_editor.action.show_object_info'),
          keybindings: showObjectInfoKeybinding,
          run: () => {
              const preferredPosition = lastHoverTargetPositionRef.current || editor.getPosition?.();
              const shown = showObjectInfoAtPosition(preferredPosition);
              if (!shown) {
                  void message.info({
                      key: 'gonavi-query-editor-object-info-miss',
                      content: translate('query_editor.message.object_info_target_not_found'),
                  });
              }
          },
      });
  }, [showObjectInfoAtPosition]);

  useEffect(() => {
      refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
  }, [currentDb, refreshObjectDecorations]);

  const insertTextIntoEditorAtPosition = useCallback((text: string, position?: { lineNumber: number; column: number } | null) => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const targetPosition = normalizeEditorPosition(position || editor?.getPosition?.() || lastEditorCursorPositionRef.current);
      if (!editor || !monaco?.Range || !targetPosition || !text) {
          return false;
      }
      editor.focus?.();
      editor.setPosition?.(targetPosition);
      editor.executeEdits?.('gonavi-sidebar-drop', [{
          range: new monaco.Range(
              targetPosition.lineNumber,
              targetPosition.column,
              targetPosition.lineNumber,
              targetPosition.column,
          ),
          text,
          forceMoveMarkers: true,
      }]);
      editor.pushUndoStop?.();
      return true;
  }, []);

  const mergeSidebarDropObjectMetadata = useCallback((payload: ReturnType<typeof decodeSidebarSqlEditorDragPayload>) => {
      if (!payload?.text || !payload.dbName) {
          return;
      }
      const nodeType = String(payload.nodeType || '').trim().toLowerCase();
      if (nodeType && nodeType !== 'table') {
          return;
      }
      const dbName = String(payload.dbName || '').trim();
      const tableName = normalizeCompletionQualifiedName(payload.text);
      if (!dbName || !tableName) {
          return;
      }
      const visibleKey = dbName.toLowerCase();
      if (!visibleDbsRef.current.some((db) => String(db || '').toLowerCase() === visibleKey)) {
          visibleDbsRef.current = [...visibleDbsRef.current, dbName];
      }
      const tableKey = `${visibleKey}\u0000${tableName.toLowerCase()}`;
      if (!tablesRef.current.some((table) => `${String(table.dbName || '').toLowerCase()}\u0000${String(table.tableName || '').toLowerCase()}` === tableKey)) {
          tablesRef.current = [...tablesRef.current, { dbName, tableName }];
      }
      if (isActive) {
          sharedVisibleDbs = visibleDbsRef.current;
          sharedTablesData = tablesRef.current;
      }
  }, [isActive]);

  const handleSidebarObjectDrop = useCallback((event: DragEvent) => {
      if (!hasSidebarSqlEditorDragPayload(event.dataTransfer)) {
          return;
      }
      event.preventDefault();
      event.stopPropagation();
      const payload = decodeSidebarSqlEditorDragPayload(String(event.dataTransfer?.getData(SIDEBAR_SQL_EDITOR_DRAG_MIME) || ''));
      const dragText = readSidebarSqlDropText(event, currentConnectionIdRef.current, currentDbRef.current);
      if (!dragText) {
          return;
      }
      const editor = editorRef.current;
      const dropTarget = editor?.getTargetAtClientPoint?.(event.clientX, event.clientY);
      if (insertTextIntoEditorAtPosition(dragText, normalizeEditorPosition(dropTarget?.position))) {
          mergeSidebarDropObjectMetadata(payload);
          refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
      }
  }, [insertTextIntoEditorAtPosition, mergeSidebarDropObjectMetadata, refreshObjectDecorations]);

  const handleSelectCurrentStatement = async () => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const model = editor?.getModel?.();
      if (!editor || !monaco?.Range || !model) {
          return;
      }

      const normalizedPosition = normalizeEditorPosition(editor.getPosition?.())
          || normalizeEditorPosition(lastEditorCursorPositionRef.current);
      if (!normalizedPosition) {
          return;
      }
      lastEditorCursorPositionRef.current = normalizedPosition;
      const lineNumber = normalizedPosition.lineNumber;
      const lineText = String(model.getLineContent?.(lineNumber) || '');
      if (!lineText.trim()) {
          void message.info(translate('query_editor.message.current_line_no_copyable_content'));
          return;
      }

      const maxColumn = Number(model.getLineMaxColumn?.(lineNumber) || 1);
      const selection = new monaco.Range(lineNumber, 1, lineNumber, maxColumn);
      editor.setPosition?.(normalizedPosition);
      editor.setSelection(selection);
      editor.revealRangeInCenterIfOutsideViewport?.(selection);

      const copied = await copyQueryEditorTextToClipboard(lineText);
      editor.setSelection(selection);
      editor.focus?.();
      if (copied) {
          void message.success(translate('data_grid.message.copied_to_clipboard'));
          return;
      }

      void message.error(translate('connection_modal.message.copy_failed'));
  };

  const handleDuplicateCurrentLine = () => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const model = editor?.getModel?.();
      const normalizedPosition = normalizeEditorPosition(editor?.getPosition?.());
      if (!editor || !monaco?.Range || !model || !normalizedPosition) {
          return;
      }

      const lineNumber = normalizedPosition.lineNumber;
      const lineText = String(model.getLineContent?.(lineNumber) || '');
      const maxColumn = Number(model.getLineMaxColumn?.(lineNumber) || (lineText.length + 1));
      const modelValue = String(model.getValue?.() || '');
      const lineBreak = typeof model.getEOL?.() === 'string'
          ? model.getEOL()
          : (modelValue.includes('\r\n') ? '\r\n' : '\n');
      const insertRange = new monaco.Range(lineNumber, maxColumn, lineNumber, maxColumn);
      const nextColumn = Math.min(normalizedPosition.column, lineText.length + 1);

      editor.executeEdits?.('gonavi-duplicate-current-line', [{
          range: insertRange,
          text: `${lineBreak}${lineText}`,
          forceMoveMarkers: true,
      }]);
      editor.pushUndoStop?.();

      const nextPosition = { lineNumber: lineNumber + 1, column: nextColumn };
      const cursorSelection = new monaco.Range(
          nextPosition.lineNumber,
          nextPosition.column,
          nextPosition.lineNumber,
          nextPosition.column,
      );
      editor.setSelections?.([cursorSelection]);
      editor.setSelection?.(cursorSelection);
      editor.setPosition?.(nextPosition);
      editor.revealLineInCenterIfOutsideViewport?.(nextPosition.lineNumber);
      editor.focus?.();

      const nextValue = editor.getValue?.();
      if (typeof nextValue === 'string') {
          applyQueryState(nextValue);
      }
  };

  const buildQueryEditorAiContextMenuActions = useCallback(() => ([
      {
          id: 'ai.generateSQL',
          label: `AI ${translate('query_editor.action.ai_generate_sql_menu')}`,
          prompt: translate('query_editor.ai_prompt.generate'),
      },
      {
          id: 'ai.explainSQL',
          label: `AI ${translate('query_editor.action.ai_explain_sql_menu')}`,
          useSelection: true,
          prompt: translate('query_editor.ai_prompt.explain', { sql: QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER }),
      },
      {
          id: 'ai.optimizeSQL',
          label: `AI ${translate('query_editor.action.ai_optimize_sql_menu')}`,
          useSelection: true,
          prompt: translate('query_editor.ai_prompt.optimize', { sql: QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER }),
      },
  ]), []);

  const disposeQueryEditorAiContextMenuActions = useCallback(() => {
      aiContextMenuActionDisposablesRef.current.forEach((disposable) => disposable?.dispose?.());
      aiContextMenuActionDisposablesRef.current = [];
  }, []);

  const registerQueryEditorAiContextMenuActions = useCallback((editor: any) => {
      disposeQueryEditorAiContextMenuActions();
      aiContextMenuActionDisposablesRef.current = buildQueryEditorAiContextMenuActions().map((action) => (
          editor.addAction({
              id: action.id,
              label: action.label,
              contextMenuGroupId: '9_ai',
              contextMenuOrder: 1,
              run: (ed: any) => {
                  const selection = ed.getModel()?.getValueInRange(ed.getSelection());
                  const conn = connectionsRef.current.find(c => c.id === currentConnectionIdRef.current);
                  const ctxText = buildQueryEditorAiContextPrompt(conn, currentDbRef.current);
                  let prompt = ctxText + action.prompt;
                  if (action.useSelection && selection) {
                      prompt = prompt.replace(QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER, selection);
                  }
                  const store = useStore.getState();
                  if (!store.aiPanelVisible) {
                      store.setAIPanelVisible(true);
                  }
                  window.dispatchEvent(new CustomEvent('gonavi:ai:inject-prompt', { detail: { prompt } }));
              },
          })
      ));
  }, [buildQueryEditorAiContextMenuActions, disposeQueryEditorAiContextMenuActions]);

  const buildQueryEditorSlashCommandDefs = useCallback(() => ([
      {
          cmd: '/query',
          label: `🔍 ${translate('query_editor.slash_command.query.label')}`,
          desc: translate('query_editor.slash_command.query.description'),
          prompt: translate('query_editor.slash_command.query.prompt'),
      },
      {
          cmd: '/sql',
          label: `📝 ${translate('query_editor.slash_command.sql.label')}`,
          desc: translate('query_editor.slash_command.sql.description'),
          prompt: translate('query_editor.slash_command.sql.prompt'),
      },
      {
          cmd: '/explain',
          label: `💡 ${translate('query_editor.slash_command.explain.label')}`,
          desc: translate('query_editor.slash_command.explain.description'),
          prompt: translate('query_editor.slash_command.explain.prompt', { sql: QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER }),
          useSelection: true,
      },
      {
          cmd: '/optimize',
          label: `⚡ ${translate('query_editor.slash_command.optimize.label')}`,
          desc: translate('query_editor.slash_command.optimize.description'),
          prompt: translate('query_editor.slash_command.optimize.prompt', { sql: QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER }),
          useSelection: true,
      },
      {
          cmd: '/schema',
          label: `🏗️ ${translate('query_editor.slash_command.schema.label')}`,
          desc: translate('query_editor.slash_command.schema.description'),
          prompt: translate('query_editor.slash_command.schema.prompt'),
      },
      {
          cmd: '/index',
          label: `📊 ${translate('query_editor.slash_command.index.label')}`,
          desc: translate('query_editor.slash_command.index.description'),
          prompt: translate('query_editor.slash_command.index.prompt'),
      },
      {
          cmd: '/diff',
          label: `🔄 ${translate('query_editor.slash_command.diff.label')}`,
          desc: translate('query_editor.slash_command.diff.description'),
          prompt: translate('query_editor.slash_command.diff.prompt'),
      },
      {
          cmd: '/mock',
          label: `🎲 ${translate('query_editor.slash_command.mock.label')}`,
          desc: translate('query_editor.slash_command.mock.description'),
          prompt: translate('query_editor.slash_command.mock.prompt'),
      },
  ]), []);

  const refreshQueryEditorSlashCommandDefs = useCallback(() => {
      (window as any).__gonaviSlashCmdDefs = buildQueryEditorSlashCommandDefs();
  }, [buildQueryEditorSlashCommandDefs]);

  const syncQueryToEditor = (sql: string) => {
      const next = sql || '';
      applyQueryState(next);
      const editor = editorRef.current;
      if (editor && editor.getValue?.() !== next) {
          editor.setValue(next);
      }
  };

  const openTextToSqlModal = useCallback(() => {
      const editor = editorRef.current;
      const selection = editor?.getSelection?.();
      const selectedText = selection ? String(editor?.getModel?.()?.getValueInRange?.(selection) || '') : '';
      setTextToSqlApplyMode(selectedText.trim() ? 'replaceSelection' : 'insert');
      setIsTextToSqlModalOpen(true);
  }, []);

  const applyTextToSqlResult = useCallback((sql: string, applyMode: QueryEditorAiApplyMode) => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      const model = editor?.getModel?.();
      const nextSql = String(sql || '').trim();
      if (!nextSql) {
          return false;
      }
      if (!editor || !monaco?.Range || !model) {
          syncQueryToEditor(nextSql);
          refreshObjectDecorations();
          return true;
      }

      const selection = editor.getSelection?.();
      const hasSelection = !!selection && !(typeof selection.isEmpty === 'function'
          ? selection.isEmpty()
          : selection.startLineNumber === selection.endLineNumber && selection.startColumn === selection.endColumn);
      const lineCount = Number(model.getLineCount?.() || 1);
      const range = applyMode === 'replaceAll'
          ? (
              model.getFullModelRange?.()
              || new monaco.Range(1, 1, lineCount, Number(model.getLineMaxColumn?.(lineCount) || 1))
          )
          : applyMode === 'replaceSelection' && hasSelection
              ? selection
              : (() => {
                  const position = normalizeEditorPosition(editor.getPosition?.())
                      || normalizeEditorPosition(lastEditorCursorPositionRef.current)
                      || { lineNumber: lineCount, column: Number(model.getLineMaxColumn?.(lineCount) || 1) };
                  return new monaco.Range(
                      position.lineNumber,
                      position.column,
                      position.lineNumber,
                      position.column,
                  );
              })();

      editor.focus?.();
      editor.pushUndoStop?.();
      editor.executeEdits?.('gonavi-text-to-sql', [{
          range,
          text: nextSql,
          forceMoveMarkers: true,
      }]);
      editor.pushUndoStop?.();
      const nextValue = String(editor.getValue?.() || nextSql);
      applyQueryState(nextValue);
      refreshObjectDecorations();
      return true;
  }, [applyQueryState, refreshObjectDecorations]);

  const showTextToSqlReadinessWarning = useCallback((reason?: string) => {
      const key = reason === 'service_unavailable'
          ? 'query_editor.message.ai_service_unavailable'
          : reason === 'model_missing'
              ? 'query_editor.message.ai_model_missing'
              : 'query_editor.message.ai_provider_missing';
      void message.warning(translate(key));
  }, []);

  const handleGenerateTextToSql = useCallback(async () => {
      const instruction = textToSqlInstruction.trim();
      if (!instruction) {
          void message.warning(translate('query_editor.message.text_to_sql_empty_instruction'));
          return;
      }

      setTextToSqlGenerating(true);
      try {
          const { sql, readiness } = await requestQueryEditorTextToSql({
              service: getQueryEditorAiService(),
              aiContext: buildQueryEditorAiContext(),
              editorSnapshot: buildQueryEditorAiEditorSnapshot(),
              instruction,
          });
          if (!readiness.ready) {
              showTextToSqlReadinessWarning(readiness.reason);
              return;
          }
          if (!sql.trim()) {
              void message.warning(translate('query_editor.message.text_to_sql_empty_result'));
              return;
          }
          if (applyTextToSqlResult(sql, textToSqlApplyMode)) {
              setIsTextToSqlModalOpen(false);
              setTextToSqlInstruction('');
              void message.success(translate('query_editor.message.text_to_sql_success'));
          }
      } catch (error: any) {
          void message.error(translate('query_editor.message.text_to_sql_failed', {
              error: error?.message || String(error || ''),
          }));
      } finally {
          setTextToSqlGenerating(false);
      }
  }, [
      applyTextToSqlResult,
      buildQueryEditorAiContext,
      buildQueryEditorAiEditorSnapshot,
      showTextToSqlReadinessWarning,
      textToSqlApplyMode,
      textToSqlInstruction,
  ]);

  // If opening a saved query, load its SQL
  useEffect(() => {
      const incoming = getTabQueryValue(tab);
      if (incoming === lastExternalQueryRef.current) {
          return;
      }
      lastExternalQueryRef.current = incoming;
      const editorHasFocus = editorRef.current?.hasTextFocus?.() === true;
      if (editorHasFocus && incoming === lastLocalQueryRef.current) {
          setQuery(incoming);
          return;
      }
      syncQueryToEditor(incoming);
  }, [tab.id, tab.query]);

  // Fetch Database List
  useEffect(() => {
      if (!autoFetchVisible) {
          return;
      }

      const fetchDbs = async () => {
          const conn = connections.find(c => c.id === currentConnectionId);
          if (!conn) return;

          const config = {
            ...conn.config,
            port: Number(conn.config.port),
            password: conn.config.password || "",
            database: conn.config.database || "",
            useSSH: conn.config.useSSH || false,
            ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
          };

          const res = await DBGetDatabases(buildRpcConnectionConfig(config) as any);
          if (res.success && Array.isArray(res.data)) {
              let dbs = res.data.map((row: any) => row.Database || row.database);

              // 过滤只显示 includeDatabases 中配置的数据库
              const includeDbs = conn.includeDatabases;
              if (includeDbs && includeDbs.length > 0) {
                  dbs = dbs.filter((db: string) => includeDbs.includes(db));
              }

              // 存储可见数据库列表用于跨库智能提示
              visibleDbsRef.current = dbs;
              if (isActive) {
                  sharedVisibleDbs = dbs;
              }

              setDbList(dbs);
              if (!currentDbRef.current) {
                  const configuredDb = String(conn.config.database || '').trim();
                  const fallbackDb = dbs.find((db: string) => String(db || '').toLowerCase() !== 'information_schema') || dbs[0] || '';
                  const nextDb = configuredDb && dbs.includes(configuredDb) ? configuredDb : fallbackDb;
                  if (nextDb) {
                      currentDbRef.current = nextDb;
                      setCurrentDb(nextDb);
                  }
              }
          } else {
              visibleDbsRef.current = [];
              if (isActive) {
                  sharedVisibleDbs = [];
              }
              setDbList([]);
          }
      };
      void fetchDbs();
  }, [autoFetchVisible, currentConnectionId, connections]);

  // Fetch Metadata for Autocomplete (Cross-database)
  useEffect(() => {
      if (!autoFetchVisible || isObjectEditQueryTab) {
          return;
      }

      let cancelled = false;
      // 仅在本次 effect 成功完成后写入；中途 cancel 不得留下 key，否则同 key 永远不再拉取 → 超链接全灭
      let activeFetchKey = '';
      const fetchMetadata = async () => {
          const conn = connections.find(c => c.id === currentConnectionId);
          if (!conn) return;

          const visibleDbs = visibleDbsRef.current;

          const config = {
            ...conn.config,
            port: Number(conn.config.port),
            password: conn.config.password || "",
            database: conn.config.database || "",
            useSSH: conn.config.useSSH || false,
            ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
          };

          const metadataDbName = String(currentDbRef.current || currentDb || '').trim();
          if (!metadataDbName) return;
          if (isActive) {
              sharedCurrentDb = metadataDbName;
          }
          const metadataDbNames = collectQueryEditorReferencedDatabaseNames(
              getCurrentQuery(),
              metadataDbName,
              visibleDbs,
          );
          const metadataFetchKey = [
              currentConnectionId,
              ...metadataDbNames.map((dbName) => String(dbName || '').toLowerCase()).sort(),
          ].join('\u0000');
          const hasCurrentDbTables = tablesRef.current.some(
              (table) => String(table.dbName || '').trim().toLowerCase() === metadataDbName.toLowerCase(),
          );
          if (metadataFetchKeyRef.current === metadataFetchKey && hasCurrentDbTables) {
              // 已成功拉过同一批库且当前库表仍在：只刷新装饰
              refreshObjectDecorations();
              return;
          }
          // key 相同但表为空（中途 cancel / 异常）：允许重拉
          activeFetchKey = metadataFetchKey;

          const allTables: CompletionTableMeta[] = [];
          const allColumns: CompletionColumnMeta[] = [];
          const allViews: CompletionViewMeta[] = [];
          const allMaterializedViews: CompletionViewMeta[] = [];
          const allTriggers: CompletionTriggerMeta[] = [];
          const allRoutines: CompletionRoutineMeta[] = [];
          const allSequences: CompletionSequenceMeta[] = [];
          const allPackages: CompletionPackageMeta[] = [];
          const metadataDialect = normalizeMetadataDialect(conn);
          const syncMetadataSnapshot = () => {
              if (cancelled) {
                  return false;
              }
              tablesRef.current = [...allTables];
              allColumnsRef.current = [...allColumns];
              viewsRef.current = [...allViews];
              materializedViewsRef.current = [...allMaterializedViews];
              triggersRef.current = [...allTriggers];
              routinesRef.current = [...allRoutines];
              sequencesRef.current = [...allSequences];
              packagesRef.current = [...allPackages];
              if (isActive) {
                  sharedCurrentDb = metadataDbName;
                  sharedTablesData = tablesRef.current;
                  sharedAllColumnsData = allColumnsRef.current;
                  sharedViewsData = viewsRef.current;
                  sharedMaterializedViewsData = materializedViewsRef.current;
                  sharedTriggersData = triggersRef.current;
                  sharedRoutinesData = routinesRef.current;
                  sharedSequencesData = sequencesRef.current;
                  sharedPackagesData = packagesRef.current;
              }
              return true;
          };

          for (const dbName of metadataDbNames) {
              if (cancelled) return;
              const tableComments = await fetchCompletionTableCommentMap(config, dbName, metadataDialect);
              if (cancelled) return;

              // 获取表
              const resTables = await DBGetTables(buildRpcConnectionConfig(config) as any, dbName);
              if (cancelled) return;
              if (resTables.success && Array.isArray(resTables.data)) {
                  resTables.data.forEach((row: any) => {
                      const tableMeta = buildCompletionTableMeta(dbName, row, tableComments);
                      if (tableMeta) {
                          allTables.push(tableMeta);
                      }
                  });
              }
              if (!syncMetadataSnapshot()) return;

              // 获取列 (所有数据库类型都支持 DBGetAllColumns)
              const resCols = await DBGetAllColumns(buildRpcConnectionConfig(config) as any, dbName);
              if (cancelled) return;
              if (resCols.success && Array.isArray(resCols.data)) {
                  resCols.data.forEach((col: any) => {
                      allColumns.push({
                          dbName,
                          tableName: col.tableName,
                          name: col.name,
                          type: col.type,
                          comment: normalizeCommentText(col.comment ?? col.Comment ?? col.COLUMN_COMMENT ?? col.column_comment ?? '')
                      });
                  });
              }
              if (!syncMetadataSnapshot()) return;

              const viewResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionViewsMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenViews = new Set<string>();
              viewResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const tableType = getCaseInsensitiveValue(row, ['table_type', 'table type', 'type']);
                      if (!isSidebarViewTableType(tableType)) return;
                      const schemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'schemaname', 'owner', 'table_schema', 'db']) || '').trim();
                      const rawViewName = String(getCaseInsensitiveValue(row, ['view_name', 'viewname', 'table_name', 'name']) || '').trim()
                          || getMySQLShowTablesName(row)
                          || getFirstRowValue(row);
                      const normalizedViewName = normalizeSidebarViewName(metadataDialect, dbName, schemaName, rawViewName);
                      if (!normalizedViewName) return;
                      const uniqueKey = `${dbName.toLowerCase()}@@${normalizedViewName.toLowerCase()}`;
                      if (seenViews.has(uniqueKey)) return;
                      seenViews.add(uniqueKey);
                      const parsed = splitSidebarQualifiedName(normalizedViewName);
                      allViews.push({
                          dbName,
                          viewName: normalizedViewName,
                          schemaName: schemaName || parsed.schemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;

              const materializedViewResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionMaterializedViewsMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenMaterializedViews = new Set<string>();
              materializedViewResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const schemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'table_schema', 'db', 'database']) || '').trim();
                      const rawViewName = String(getCaseInsensitiveValue(row, ['object_name', 'view_name', 'table_name', 'name', 'materialized_view_name', 'mv_name']) || '').trim() || getFirstRowValue(row);
                      const normalizedViewName = normalizeSidebarViewName(metadataDialect, dbName, schemaName, rawViewName);
                      if (!normalizedViewName) return;
                      const uniqueKey = `${dbName.toLowerCase()}@@${normalizedViewName.toLowerCase()}`;
                      if (seenMaterializedViews.has(uniqueKey)) return;
                      seenMaterializedViews.add(uniqueKey);
                      const parsed = splitSidebarQualifiedName(normalizedViewName);
                      allMaterializedViews.push({
                          dbName,
                          viewName: normalizedViewName,
                          schemaName: schemaName || parsed.schemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;

              const triggerResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionTriggersMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenTriggers = new Set<string>();
              triggerResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const rawTriggerName = String(getCaseInsensitiveValue(row, ['trigger_name', 'triggername', 'trigger', 'name']) || '').trim() || getFirstRowValue(row);
                      if (!rawTriggerName) return;
                      const rawSchemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'schemaname', 'owner', 'event_object_schema', 'trigger_schema', 'db']) || '').trim();
                      const rawTableName = String(getCaseInsensitiveValue(row, ['table_name', 'event_object_table', 'tbl_name', 'table']) || '').trim();
                      const triggerParts = splitSidebarQualifiedName(rawTriggerName);
                      const tableParts = splitSidebarQualifiedName(rawTableName);
                      const resolvedSchemaName = String(rawSchemaName || tableParts.schemaName || triggerParts.schemaName || '').trim();
                      const resolvedTriggerName = String(triggerParts.objectName || rawTriggerName).trim();
                      const resolvedTableName = buildQualifiedCompletionName(resolvedSchemaName, tableParts.objectName || rawTableName);
                      const uniqueKey = (metadataDialect === 'mysql' || metadataDialect === 'starrocks')
                          ? `${dbName.toLowerCase()}@@${resolvedSchemaName.toLowerCase()}@@${resolvedTriggerName.toLowerCase()}`
                          : `${dbName.toLowerCase()}@@${resolvedSchemaName.toLowerCase()}@@${resolvedTriggerName.toLowerCase()}@@${resolvedTableName.toLowerCase()}`;
                      if (seenTriggers.has(uniqueKey)) return;
                      seenTriggers.add(uniqueKey);
                      allTriggers.push({
                          dbName,
                          triggerName: buildQualifiedCompletionName(resolvedSchemaName, resolvedTriggerName) || resolvedTriggerName,
                          tableName: resolvedTableName || rawTableName,
                          schemaName: resolvedSchemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;

              const routineResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionFunctionsMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenRoutines = new Set<string>();
              routineResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const rawRoutineName = String(getCaseInsensitiveValue(row, ['routine_name', 'object_name', 'proname', 'name']) || '').trim();
                      if (!rawRoutineName) return;
                      const schemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'nspname', 'owner', 'db', 'database']) || '').trim();
                      const rawType = String(getCaseInsensitiveValue(row, ['routine_type', 'object_type', 'type']) || queryResult.inferredType || 'FUNCTION').trim();
                      const normalizedType = rawType.toUpperCase().includes('PROC') ? 'PROCEDURE' : 'FUNCTION';
                      const qualifiedRoutineName = buildQualifiedCompletionName(schemaName, rawRoutineName);
                      if (!qualifiedRoutineName) return;
                      const uniqueKey = `${dbName.toLowerCase()}@@${qualifiedRoutineName.toLowerCase()}@@${normalizedType}`;
                      if (seenRoutines.has(uniqueKey)) return;
                      seenRoutines.add(uniqueKey);
                      allRoutines.push({
                          dbName,
                          routineName: qualifiedRoutineName,
                          routineType: normalizedType,
                          schemaName: schemaName || splitSidebarQualifiedName(qualifiedRoutineName).schemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;

              const sequenceResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionSequencesMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenSequences = new Set<string>();
              sequenceResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const rawSequenceName = String(getCaseInsensitiveValue(row, ['sequence_name', 'name']) || '').trim() || getFirstRowValue(row);
                      if (!rawSequenceName) return;
                      const schemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'sequence_owner', 'owner', 'db', 'database']) || '').trim();
                      const sequenceParts = splitSidebarQualifiedName(rawSequenceName);
                      const resolvedSchemaName = String(schemaName || sequenceParts.schemaName || '').trim();
                      const resolvedSequenceName = String(sequenceParts.objectName || rawSequenceName).trim();
                      const qualifiedSequenceName = buildQualifiedCompletionName(resolvedSchemaName, resolvedSequenceName);
                      if (!qualifiedSequenceName) return;
                      const uniqueKey = `${dbName.toLowerCase()}@@${qualifiedSequenceName.toLowerCase()}`;
                      if (seenSequences.has(uniqueKey)) return;
                      seenSequences.add(uniqueKey);
                      allSequences.push({
                          dbName,
                          sequenceName: qualifiedSequenceName,
                          schemaName: resolvedSchemaName || splitSidebarQualifiedName(qualifiedSequenceName).schemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;

              const packageResults = await queryCompletionMetadataRowsBySpecs(
                  config,
                  dbName,
                  buildCompletionPackagesMetadataQuerySpecs(metadataDialect, dbName),
              );
              if (cancelled) return;
              const seenPackages = new Set<string>();
              packageResults.forEach((queryResult) => {
                  queryResult.rows.forEach((row) => {
                      const rawPackageName = String(getCaseInsensitiveValue(row, ['package_name', 'object_name', 'name']) || '').trim() || getFirstRowValue(row);
                      if (!rawPackageName) return;
                      const schemaName = String(getCaseInsensitiveValue(row, ['schema_name', 'owner', 'db', 'database']) || '').trim();
                      const packageParts = splitSidebarQualifiedName(rawPackageName);
                      const resolvedSchemaName = String(schemaName || packageParts.schemaName || '').trim();
                      const resolvedPackageName = String(packageParts.objectName || rawPackageName).trim();
                      const qualifiedPackageName = buildQualifiedCompletionName(resolvedSchemaName, resolvedPackageName);
                      if (!qualifiedPackageName) return;
                      const uniqueKey = `${dbName.toLowerCase()}@@${qualifiedPackageName.toLowerCase()}`;
                      if (seenPackages.has(uniqueKey)) return;
                      seenPackages.add(uniqueKey);
                      allPackages.push({
                          dbName,
                          packageName: qualifiedPackageName,
                          schemaName: resolvedSchemaName || splitSidebarQualifiedName(qualifiedPackageName).schemaName || undefined,
                      });
                  });
              });
              if (!syncMetadataSnapshot()) return;
          }

          if (!syncMetadataSnapshot()) return;
          // 成功完成后才固化 key，避免 cancel 后同 key 被误判为「已完成」
          metadataFetchKeyRef.current = activeFetchKey;
          lastSqlReferencedMetadataKeyRef.current = activeFetchKey;
          refreshObjectDecorations();
      };
      void fetchMetadata();
      return () => {
          cancelled = true;
      };
  }, [
      autoFetchVisible,
      currentConnectionId,
      currentDb,
      connections,
      isActive,
      isObjectEditQueryTab,
      refreshObjectDecorations,
      sqlReferencedMetadataKey,
  ]);

  // Query ID management helpers
  const setQueryId = (id: string) => {
      currentQueryIdRef.current = id;
      setCurrentQueryId(id);
  };

  const clearQueryId = () => {
      currentQueryIdRef.current = '';
      setCurrentQueryId('');
  };

  const resolveEditorSplitAvailableHeight = useCallback(() => {
      const rootRect = queryEditorRootRef.current?.getBoundingClientRect?.();
      const paneRect = editorPaneRef.current?.getBoundingClientRect?.();
      const editorContainerRect = (editorStageRef.current || editorShellRef.current)?.getBoundingClientRect?.();
      const rootHeight = Number(rootRect?.height || 0);
      const paneHeight = Number(paneRect?.height || 0);
      const editorContainerHeight = Number(editorContainerRect?.height || 0);
      if (!Number.isFinite(rootHeight) || rootHeight <= 0) {
          return 0;
      }
      const nonEditorPaneHeight = paneHeight > 0 && editorContainerHeight > 0
          ? Math.max(0, paneHeight - editorContainerHeight)
          : 0;
      const availableHeight = rootHeight - nonEditorPaneHeight;
      return Number.isFinite(availableHeight) && availableHeight > 0 ? availableHeight : 0;
  }, []);

  const clampEditorHeight = useCallback((height: number) => {
      const availableHeight = resolveEditorSplitAvailableHeight();
      if (availableHeight > 0) {
          return clampQueryEditorEditorHeight(height, availableHeight);
      }
      const viewportHeight = Number.isFinite(window.innerHeight) ? window.innerHeight : 800;
      const maxHeight = Math.max(100, viewportHeight - 200);
      return Math.max(100, Math.min(maxHeight, height));
  }, [resolveEditorSplitAvailableHeight]);

  const applyEditorHeightRatio = useCallback(() => {
      const availableHeight = resolveEditorSplitAvailableHeight();
      if (availableHeight <= 0 || dragRef.current) return;
      const nextHeight = resolveQueryEditorEditorHeightFromRatio(
          queryEditorEditorHeightRatio,
          availableHeight,
      );
      pendingEditorHeightRef.current = nextHeight;
      setEditorHeight(previousHeight => previousHeight === nextHeight ? previousHeight : nextHeight);
  }, [queryEditorEditorHeightRatio, resolveEditorSplitAvailableHeight]);

  useEffect(() => {
      if (!isResultPanelVisible || !isActive) return;
      let frame: number | null = null;
      const requestFrame = typeof window.requestAnimationFrame === 'function'
          ? window.requestAnimationFrame.bind(window)
          : (callback: FrameRequestCallback) => window.setTimeout(() => callback(Date.now()), 16);
      const cancelFrame = typeof window.cancelAnimationFrame === 'function'
          ? window.cancelAnimationFrame.bind(window)
          : window.clearTimeout.bind(window);
      const scheduleApply = () => {
          if (frame !== null) return;
          frame = requestFrame(() => {
              frame = null;
              applyEditorHeightRatio();
          });
      };

      scheduleApply();
      const ResizeObserverCtor = typeof ResizeObserver === 'function' ? ResizeObserver : null;
      const resizeObserver = ResizeObserverCtor ? new ResizeObserverCtor(scheduleApply) : null;
      if (resizeObserver) {
          if (queryEditorRootRef.current) resizeObserver.observe(queryEditorRootRef.current);
          if (editorPaneRef.current) resizeObserver.observe(editorPaneRef.current);
      }
      window.addEventListener('resize', scheduleApply);
      return () => {
          if (frame !== null) {
              cancelFrame(frame);
              frame = null;
          }
          resizeObserver?.disconnect();
          window.removeEventListener('resize', scheduleApply);
      };
  }, [applyEditorHeightRatio, isActive, isResultPanelVisible, tab.id]);

  const applyEditorHeightToDom = useCallback(() => {
      const nextHeight = pendingEditorHeightRef.current;
      const editorContainer = editorStageRef.current || editorShellRef.current;
      if (editorContainer) {
          editorContainer.style.height = `${nextHeight}px`;
      }
      editorRef.current?.layout?.();
  }, []);

  const cancelEditorResizeFrame = useCallback(() => {
      if (resizeFrameRef.current === null) return;
      if (typeof window.cancelAnimationFrame === 'function') {
          window.cancelAnimationFrame(resizeFrameRef.current);
      } else {
          window.clearTimeout(resizeFrameRef.current);
      }
      resizeFrameRef.current = null;
  }, []);

  const scheduleEditorHeightDomUpdate = useCallback((height: number) => {
      pendingEditorHeightRef.current = height;
      if (resizeFrameRef.current !== null) return;

      const requestFrame = typeof window.requestAnimationFrame === 'function'
          ? window.requestAnimationFrame.bind(window)
          : (callback: FrameRequestCallback) => window.setTimeout(() => callback(Date.now()), 16);

      resizeFrameRef.current = requestFrame(() => {
          resizeFrameRef.current = null;
          applyEditorHeightToDom();
      });
  }, [applyEditorHeightToDom]);

  // Handle Resizing
  const handleMouseMove = useCallback((e: MouseEvent) => {
      if (!dragRef.current) return;
      const delta = e.clientY - dragRef.current.startY;
      const newHeight = clampEditorHeight(dragRef.current.startHeight + delta);
      dragRef.current.currentHeight = newHeight;
      scheduleEditorHeightDomUpdate(newHeight);
  }, [clampEditorHeight, scheduleEditorHeightDomUpdate]);

  const handleMouseUp = useCallback(() => {
      const finalHeight = dragRef.current?.currentHeight;
      dragRef.current = null;
      cancelEditorResizeFrame();
      if (typeof finalHeight === 'number') {
          pendingEditorHeightRef.current = finalHeight;
          applyEditorHeightToDom();
          setEditorHeight(finalHeight);
          const availableHeight = resolveEditorSplitAvailableHeight();
          if (availableHeight > 0) {
              setQueryOptions({
                  queryEditorEditorHeightRatio: resolveQueryEditorEditorHeightRatio(
                      finalHeight,
                      availableHeight,
                  ),
              });
          }
      }
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
  }, [applyEditorHeightToDom, cancelEditorResizeFrame, handleMouseMove, resolveEditorSplitAvailableHeight, setQueryOptions]);

  const handleMouseDown = useCallback((e: React.MouseEvent) => {
      e.preventDefault();
      const currentEditorHeight = Number((editorStageRef.current || editorShellRef.current)?.getBoundingClientRect?.().height || editorHeight);
      const startHeight = Number.isFinite(currentEditorHeight) && currentEditorHeight > 0 ? currentEditorHeight : editorHeight;
      dragRef.current = { startY: e.clientY, startHeight, currentHeight: startHeight };
      pendingEditorHeightRef.current = startHeight;
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
  }, [editorHeight, handleMouseMove, handleMouseUp]);

  useEffect(() => {
      return () => {
          dragRef.current = null;
          cancelEditorResizeFrame();
          document.removeEventListener('mousemove', handleMouseMove);
          document.removeEventListener('mouseup', handleMouseUp);
      };
  }, [cancelEditorResizeFrame, handleMouseMove, handleMouseUp]);

  const openRoutineObjectEditTab = useCallback(async (
      navigationTarget: Extract<QueryEditorNavigationTarget, { type: 'routine' }>,
      connectionId: string,
      targetDbName: string,
  ) => {
      const targetRoutineName = String(navigationTarget.routineName || '').trim();
      if (!targetRoutineName) return;

      const normalizedRoutineType = String(navigationTarget.routineType || 'FUNCTION').trim().toUpperCase().includes('PROC')
          ? 'PROCEDURE'
          : 'FUNCTION';
      const routineTypeLabel = normalizedRoutineType === 'PROCEDURE'
          ? translate('sidebar.object.procedure')
          : translate('sidebar.object.function');
      const sqlTemplateHeader = `-- ${translate('sidebar.sql_template.edit_routine', {
          type: routineTypeLabel,
          name: targetRoutineName,
      })}`;
      let editSql = `${sqlTemplateHeader}\n-- ${translate('sidebar.sql_template.modify_then_execute')}\n${buildQueryEditorRoutineEditFallbackSql(targetRoutineName, normalizedRoutineType)}`;

      const conn = connectionsRef.current.find((item) => item.id === connectionId);
      if (conn) {
          const dialect = normalizeMetadataDialect(conn);
          const parsedRoutine = splitSidebarQualifiedName(targetRoutineName);
          const routineObjectName = parsedRoutine.objectName || targetRoutineName;
          const routineSchemaName = String(navigationTarget.schemaName || parsedRoutine.schemaName || '').trim();
          const safeName = escapeQueryEditorObjectEditSqlLiteral(routineObjectName);
          const safeSchema = escapeQueryEditorObjectEditSqlLiteral(routineSchemaName);
          const safeDbName = escapeQueryEditorObjectEditSqlLiteral(targetDbName);
          const config = {
              ...conn.config,
              port: Number(conn.config?.port),
              password: conn.config?.password || '',
              database: conn.config?.database || '',
              useSSH: conn.config?.useSSH || false,
              ssh: conn.config?.ssh || { host: '', port: 22, user: '', password: '', keyPath: '' },
          };
          const queries = (() => {
              switch (dialect) {
                  case 'mysql':
                  case 'starrocks':
                      return [
                          `SHOW CREATE ${normalizedRoutineType} \`${routineObjectName.replace(/`/g, '``')}\``,
                          safeDbName
                              ? `SELECT ROUTINE_DEFINITION AS routine_definition FROM information_schema.routines WHERE routine_schema = '${safeDbName}' AND routine_name = '${safeName}' AND UPPER(routine_type) = '${normalizedRoutineType}' LIMIT 1`
                              : '',
                      ].filter(Boolean);
                  case 'postgres':
                  case 'kingbase':
                  case 'highgo':
                  case 'vastbase':
                  case 'opengauss':
                  case 'gaussdb': {
                      const schemaRef = safeSchema || 'public';
                      return [`SELECT pg_get_functiondef(p.oid) AS routine_definition FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid WHERE n.nspname = '${schemaRef}' AND p.proname = '${safeName}' LIMIT 1`];
                  }
                  case 'sqlserver':
                      return buildSqlServerObjectDefinitionQueries('routine', targetRoutineName, targetDbName, 'routine_definition');
                  case 'oracle':
                  case 'dm':
                  case 'dameng': {
                      const owner = safeSchema || safeDbName;
                      return [
                          owner
                              ? `SELECT TEXT FROM ALL_SOURCE WHERE OWNER = '${owner.toUpperCase()}' AND NAME = '${safeName.toUpperCase()}' AND TYPE = '${normalizedRoutineType}' ORDER BY LINE`
                              : `SELECT TEXT FROM USER_SOURCE WHERE NAME = '${safeName.toUpperCase()}' AND TYPE = '${normalizedRoutineType}' ORDER BY LINE`,
                      ];
                  }
                  case 'duckdb': {
                      const schemaRef = safeSchema || 'main';
                      return [
                          `SELECT schema_name, function_name, parameters, macro_definition FROM duckdb_functions() WHERE internal = false AND lower(function_type) = 'macro' AND schema_name = '${schemaRef}' AND function_name = '${safeName}' LIMIT 1`,
                      ];
                  }
                  default:
                      return [];
              }
          })();

          for (const queryText of queries) {
              try {
                  const result = await DBQuery(buildRpcConnectionConfig(config) as any, targetDbName, queryText);
                  if (!result.success || !Array.isArray(result.data) || result.data.length === 0) {
                      continue;
                  }
                  let definition = '';
                  if (dialect === 'oracle' || dialect === 'dm' || dialect === 'dameng') {
                      definition = result.data.map((row: any) => row.text || row.TEXT || Object.values(row)[0] || '').join('');
                  } else if (dialect === 'duckdb') {
                      const row = result.data[0] as Record<string, any>;
                      const schemaName = String(getQueryEditorObjectEditRawValue(row, ['schema_name']) || routineSchemaName || '').trim();
                      const functionName = String(getQueryEditorObjectEditRawValue(row, ['function_name', 'routine_name', 'name']) || routineObjectName || '').trim();
                      const parametersRaw = getQueryEditorObjectEditRawValue(row, ['parameters']);
                      const macroDefinition = String(getQueryEditorObjectEditRawValue(row, ['macro_definition']) || '').trim();
                      const parameters = Array.isArray(parametersRaw)
                          ? parametersRaw.map((item) => String(item ?? '').trim()).filter(Boolean).join(', ')
                          : String(parametersRaw ?? '').replace(/^\[|\]$/g, '').trim();
                      const qualifiedName = schemaName ? `${schemaName}.${functionName}` : functionName;
                      if (qualifiedName && macroDefinition) {
                          definition = macroDefinition.startsWith('(')
                              ? `CREATE OR REPLACE MACRO ${qualifiedName}(${parameters}) AS ${macroDefinition};`
                              : `CREATE OR REPLACE MACRO ${qualifiedName}(${parameters}) AS TABLE ${macroDefinition};`;
                      }
                  } else if (dialect === 'sqlserver') {
                      definition = result.data
                          .map((row: any) => getQueryEditorObjectEditRawValue(row, ['routine_definition', 'definition', 'text', 'Text']) ?? '')
                          .map((value) => String(value))
                          .join('');
                  } else {
                      const row = result.data[0] as Record<string, any>;
                      const direct = getQueryEditorObjectEditRawValue(row, ['routine_definition', 'definition']);
                      if (direct !== undefined && direct !== null && String(direct).trim()) {
                          definition = String(direct);
                      } else {
                          const createKey = Object.keys(row).find((key) => /create\s+(function|procedure)/i.test(key));
                          definition = createKey ? String(row[createKey] || '') : '';
                      }
                  }

                  const normalizedDefinition = normalizeQueryEditorRoutineDefinitionForEdit(
                      definition,
                      targetRoutineName,
                      normalizedRoutineType,
                  );
                  if (normalizedDefinition) {
                      editSql = `${sqlTemplateHeader}\n${normalizedDefinition}`;
                      break;
                  }
              } catch {
                  // 查询最新定义失败时保留可编辑模板。
              }
          }
      }

      addTab({
          id: `query-edit-routine-${connectionId}-${targetDbName}-${targetRoutineName}-${Date.now()}`,
          title: translate('sidebar.tab.edit_routine', {
              type: routineTypeLabel,
              name: targetRoutineName,
          }),
          type: 'query',
          connectionId,
          dbName: targetDbName,
          query: editSql,
          queryMode: 'object-edit',
          returnToTabId: activeTabId || undefined,
      });
  }, [activeTabId, addTab]);

  const openDefinitionObjectEditTab = useCallback(async (
      navigationTarget: Extract<QueryEditorNavigationTarget, { type: 'view' | 'materialized-view' | 'sequence' | 'package' }>,
      connectionId: string,
      targetDbName: string,
  ) => {
      const targetSchemaName = String(navigationTarget.schemaName || '').trim();
      const conn = connectionsRef.current.find((item) => item.id === connectionId);
      const dialect = conn ? normalizeMetadataDialect(conn) : '';
      let targetObjectName = '';
      let objectEditName = '';
      let objectLabel = '';
      let definitionTabType: 'view-def' | 'sequence-def' | 'package-def' = 'view-def';
      let definitionQueries: string[] = [];
      let collectAllDefinitionRows = false;
      let latestDefinition = '';

      if (navigationTarget.type === 'view' || navigationTarget.type === 'materialized-view') {
          targetObjectName = String(navigationTarget.viewName || '').trim();
          if (!targetObjectName) return;
          definitionTabType = 'view-def';
          objectEditName = buildQueryEditorQualifiedObjectName(targetObjectName, targetSchemaName);
          objectLabel = navigationTarget.type === 'materialized-view'
              ? translate('definition_viewer.object.materialized_view')
              : translate('definition_viewer.object.view');
          definitionQueries = conn
              ? buildQueryEditorViewDefinitionQueries(
                  dialect,
                  targetObjectName,
                  targetDbName,
                  targetSchemaName,
                  navigationTarget.type === 'materialized-view' ? 'materialized' : 'view',
              )
              : [];
      } else if (navigationTarget.type === 'sequence') {
          targetObjectName = String(navigationTarget.sequenceName || '').trim();
          if (!targetObjectName) return;
          definitionTabType = 'sequence-def';
          objectEditName = buildQueryEditorQualifiedObjectName(targetObjectName, targetSchemaName);
          objectLabel = translate('definition_viewer.object.sequence');
          definitionQueries = conn
              ? buildQueryEditorSequenceDefinitionQueries(dialect, targetObjectName, targetDbName, targetSchemaName)
              : [];
      } else {
          targetObjectName = String(navigationTarget.packageName || '').trim();
          if (!targetObjectName) return;
          definitionTabType = 'package-def';
          objectEditName = buildQueryEditorQualifiedObjectName(targetObjectName, targetSchemaName);
          objectLabel = translate('definition_viewer.object.package');
          collectAllDefinitionRows = true;
          definitionQueries = conn
              ? buildQueryEditorPackageDefinitionQueries(dialect, targetObjectName, targetDbName, targetSchemaName)
              : [];
      }

      if (conn && definitionQueries.length > 0) {
          const rows = await runQueryEditorObjectDefinitionCandidates(
              buildQueryEditorObjectDefinitionConnectionConfig(conn),
              targetDbName,
              definitionQueries,
              collectAllDefinitionRows,
          );
          if (definitionTabType === 'view-def') {
              latestDefinition = extractQueryEditorViewDefinition(dialect, rows);
          } else if (definitionTabType === 'sequence-def') {
              latestDefinition = extractQueryEditorSequenceDefinition(rows, targetObjectName, targetSchemaName);
          } else {
              latestDefinition = extractQueryEditorPackageDefinition(rows);
          }
      }

      addTab({
          id: `query-edit-object-${connectionId}-${targetDbName}-${objectEditName}-${Date.now()}`,
          title: translate('definition_viewer.edit.tab_title', {
              object: objectLabel,
              name: objectEditName,
          }),
          type: 'query',
          connectionId,
          dbName: targetDbName,
          query: buildQueryEditorEditableDefinitionSql(
              definitionTabType,
              latestDefinition,
              objectEditName,
              objectLabel,
          ),
          queryMode: 'object-edit',
          returnToTabId: activeTabId || undefined,
      });
  }, [activeTabId, addTab]);

  const openTriggerObjectEditTab = useCallback(async (
      navigationTarget: Extract<QueryEditorNavigationTarget, { type: 'trigger' }>,
      connectionId: string,
      targetDbName: string,
  ) => {
      const targetTriggerName = String(navigationTarget.triggerName || '').trim();
      if (!targetTriggerName) return;

      const conn = connectionsRef.current.find((item) => item.id === connectionId);
      let latestDefinition = '';
      if (conn) {
          const dialect = normalizeMetadataDialect(conn);
          const rows = await runQueryEditorObjectDefinitionCandidates(
              buildQueryEditorObjectDefinitionConnectionConfig(conn),
              targetDbName,
              buildQueryEditorTriggerDefinitionQueries(
                  dialect,
                  targetTriggerName,
                  targetDbName,
                  navigationTarget.schemaName,
              ),
          );
          latestDefinition = extractQueryEditorTriggerDefinition(dialect, rows);
      }

      addTab({
          id: `query-edit-trigger-${connectionId}-${targetDbName}-${targetTriggerName}-${Date.now()}`,
          title: translate('trigger_viewer.tab.edit_trigger_title', { name: targetTriggerName }),
          type: 'query',
          connectionId,
          dbName: targetDbName,
          query: buildEditableTriggerSql(targetTriggerName, latestDefinition, { translate }),
          queryMode: 'object-edit',
          returnToTabId: activeTabId || undefined,
      });
  }, [activeTabId, addTab]);

  // Setup Autocomplete and Editor
  const handleEditorDidMount: OnMount = (editor, monaco) => {
      editorRef.current = editor;
      monacoRef.current = monaco;

      const suggestController = editor.getContribution?.('editor.contrib.suggestController') as {
          widget?: { value?: { _details?: { widget?: { layout?: (width: number, height: number) => void } } } };
      } | null;
      const suggestDetailsWidget = suggestController?.widget?.value?._details?.widget;
      if (suggestDetailsWidget?.layout) {
          const originalSuggestDetailsLayout = suggestDetailsWidget.layout.bind(suggestDetailsWidget);
          suggestDetailsWidget.layout = (width: number, height: number) => {
              originalSuggestDetailsLayout(width, Math.max(height, QUERY_EDITOR_SQL_SNIPPET_SUGGEST_DETAIL_MIN_HEIGHT));
          };
      }
      lastEditorCursorPositionRef.current = normalizeEditorPosition(editor.getPosition?.());
      if (isActive) {
          sharedActiveEditorModelUri = String(editor.getModel?.()?.uri?.toString?.() || '');
      }

      const mountedModel = editor.getModel?.();
      if (mountedModel && typeof monaco?.editor?.setModelLanguage === 'function') {
          monaco.editor.setModelLanguage(mountedModel, 'sql');
      }
      editor.updateOptions?.(buildQueryEditorMonacoOptions(isObjectEditQueryTab));

      aiInlineGhostVisibleContextKeyRef.current = editor.createContextKey?.(
          QUERY_EDITOR_AI_INLINE_CONTEXT_KEY,
          false,
      ) || null;

      const clearAiInlineGhostTimer = () => {
          if (aiInlineGhostTimerRef.current !== null) {
              clearTimeout(aiInlineGhostTimerRef.current);
              aiInlineGhostTimerRef.current = null;
          }
      };

      const clearAiInlineGhostDecorations = () => {
          if (aiInlineGhostDecorationIdsRef.current.length === 0) {
              return;
          }
          const nextDecorationIds = editor.deltaDecorations?.(
              aiInlineGhostDecorationIdsRef.current,
              [],
          );
          aiInlineGhostDecorationIdsRef.current = Array.isArray(nextDecorationIds) ? nextDecorationIds : [];
      };

      const clearAiInlineGhost = (cancelRequest = true) => {
          clearAiInlineGhostTimer();
          if (cancelRequest) {
              aiInlineGhostRequestSeqRef.current += 1;
          }
          aiInlineGhostRef.current = null;
          aiInlineGhostVisibleContextKeyRef.current?.set?.(false);
          if (aiInlineGhostOverlayRef.current) {
              aiInlineGhostOverlayRef.current.remove();
              aiInlineGhostOverlayRef.current = null;
          }
          clearAiInlineGhostDecorations();
      };

      const triggerStructuredSqlSuggest = (source: string, defer = false) => {
          const run = () => {
              if (editorRef.current !== editor) {
                  return;
              }
              editor.trigger?.(source, 'editor.action.triggerSuggest', undefined);
          };
          if (defer) {
              window.setTimeout(run, 0);
              return;
          }
          run();
      };

      const didModelContentAcceptCurrentAiInlineGhost = (event: any): boolean => {
          const ghost = aiInlineGhostRef.current;
          if (!ghost?.insertText) {
              return false;
          }
          const changes = Array.isArray(event?.changes) ? event.changes : [];
          return changes.some((change: any) => String(change?.text ?? '') === ghost.insertText);
      };

      const buildInlineGhostEditorSnapshot = (model: any, position: { lineNumber: number; column: number }): QueryEditorAiEditorSnapshot => {
          const lineContent = String(model.getLineContent?.(position.lineNumber) || '');
          const lineColumnIndex = Math.max(0, Math.min(Number(position.column || 1) - 1, lineContent.length));
          const lineCount = Number(model.getLineCount?.() || position.lineNumber || 1);
          return {
              prefix: String(model.getValueInRange?.(new monaco.Range(1, 1, position.lineNumber, position.column)) || ''),
              suffix: String(model.getValueInRange?.(new monaco.Range(
                  position.lineNumber,
                  position.column,
                  lineCount,
                  Number(model.getLineMaxColumn?.(lineCount) || position.column),
              )) || ''),
              currentLineBeforeCursor: lineContent.slice(0, lineColumnIndex),
              currentLineAfterCursor: lineContent.slice(lineColumnIndex),
          };
      };

      const buildInlineGhostEditorSnapshotFromInsertedTextRemoval = (
          modelText: string,
          rangeOffset: number,
          removedTextLength: number,
      ): QueryEditorAiEditorSnapshot | null => {
          if (!Number.isFinite(rangeOffset)) {
              return null;
          }
          const safeStart = Math.max(0, Math.min(Math.trunc(rangeOffset), modelText.length));
          const safeEnd = Math.max(safeStart, Math.min(safeStart + Math.max(0, removedTextLength), modelText.length));
          const textBeforeInsertion = `${modelText.slice(0, safeStart)}${modelText.slice(safeEnd)}`;
          const prefix = textBeforeInsertion.slice(0, safeStart);
          const suffix = textBeforeInsertion.slice(safeStart);
          const lineStart = Math.max(0, prefix.lastIndexOf('\n') + 1);
          const nextLineBreak = textBeforeInsertion.indexOf('\n', safeStart);
          const lineEnd = nextLineBreak === -1 ? textBeforeInsertion.length : nextLineBreak;
          return {
              prefix,
              suffix,
              currentLineBeforeCursor: prefix.slice(lineStart).replace(/\r/g, ''),
              currentLineAfterCursor: textBeforeInsertion.slice(safeStart, lineEnd).replace(/\r/g, ''),
          };
      };

      const recoverStrayManualSqlCompletionMarker = (
          model: any,
          position: { lineNumber: number; column: number },
          snapshot: QueryEditorAiEditorSnapshot,
      ): {
          position: { lineNumber: number; column: number };
          snapshot: QueryEditorAiEditorSnapshot;
          recovered: boolean;
      } => {
          const prefix = String(snapshot.prefix || '');
          const lineBeforeCursor = String(snapshot.currentLineBeforeCursor || '');
          if (!prefix.endsWith('\\') || !lineBeforeCursor.endsWith('\\')) {
              return { position, snapshot, recovered: false };
          }

          const sanitizedSnapshot: QueryEditorAiEditorSnapshot = {
              prefix: prefix.slice(0, -1),
              suffix: String(snapshot.suffix || ''),
              currentLineBeforeCursor: lineBeforeCursor.slice(0, -1),
              currentLineAfterCursor: String(snapshot.currentLineAfterCursor || ''),
          };
          const intent = resolveQueryEditorInlineCompletionIntentDetails(sanitizedSnapshot);
          if (intent.intent !== 'table_name' && intent.intent !== 'column_name') {
              return { position, snapshot, recovered: false };
          }

          const startColumn = Math.max(1, position.column - 1);
          const startPosition = { lineNumber: position.lineNumber, column: startColumn };
          editor.executeEdits?.('gonavi-manual-sql-ai-strip-marker', [{
              range: new monaco.Range(
                  position.lineNumber,
                  startColumn,
                  position.lineNumber,
                  position.column,
              ),
              text: '',
              forceMoveMarkers: true,
          }]);
          editor.setPosition?.(startPosition);
          syncQueryDraft(getEditorText());

          return {
              position: startPosition,
              snapshot: buildInlineGhostEditorSnapshot(model, startPosition),
              recovered: true,
          };
      };

      const isInlineGhostSnapshotCurrent = (
          model: any,
          position: { lineNumber: number; column: number },
          snapshot: QueryEditorAiEditorSnapshot,
      ): boolean => {
          const currentSnapshot = buildInlineGhostEditorSnapshot(model, position);
          return currentSnapshot.prefix === snapshot.prefix
              && currentSnapshot.suffix === snapshot.suffix
              && currentSnapshot.currentLineBeforeCursor === snapshot.currentLineBeforeCursor
              && currentSnapshot.currentLineAfterCursor === snapshot.currentLineAfterCursor;
      };

      const renderAiInlineGhost = (
          model: any,
          position: { lineNumber: number; column: number },
          insertText: string,
          snapshot: QueryEditorAiEditorSnapshot,
      ) => {
          const previewText = resolveInlineSqlGhostPreviewText(insertText);
          if (!previewText) {
              clearAiInlineGhost(false);
              return;
          }

          const modelUri = String(model?.uri?.toString?.() || '');
          aiInlineGhostRef.current = {
              insertText,
              modelUri,
              position,
              snapshot,
          };
          clearAiInlineGhostDecorations();
          const visiblePosition = editor.getScrolledVisiblePosition?.(position);
          const editorDomNode = editor.getDomNode?.();
          if (!visiblePosition || !editorDomNode) {
              clearAiInlineGhost(false);
              return;
          }

          const overlay = aiInlineGhostOverlayRef.current || document.createElement('span');
          if (!aiInlineGhostOverlayRef.current) {
              overlay.className = 'gonavi-query-editor-ai-inline-ghost-overlay';
              editorDomNode.appendChild(overlay);
              aiInlineGhostOverlayRef.current = overlay;
          }

          const fontInfoOption = monaco.editor?.EditorOption?.fontInfo;
          const fontInfo = fontInfoOption !== undefined ? editor.getOption?.(fontInfoOption) : null;
          overlay.textContent = previewText;
          overlay.style.left = `${Math.max(0, visiblePosition.left)}px`;
          overlay.style.top = `${Math.max(0, visiblePosition.top)}px`;
          overlay.style.height = `${Math.max(1, visiblePosition.height || fontInfo?.lineHeight || 20)}px`;
          overlay.style.lineHeight = `${Math.max(1, visiblePosition.height || fontInfo?.lineHeight || 20)}px`;
          if (fontInfo) {
              overlay.style.fontFamily = String(fontInfo.fontFamily || '');
              overlay.style.fontSize = `${Number(fontInfo.fontSize || 14)}px`;
              overlay.style.fontWeight = String(fontInfo.fontWeight || 'normal');
          }
          aiInlineGhostVisibleContextKeyRef.current?.set?.(true);
      };

      const acceptAiInlineGhost = (): boolean => {
          const ghost = aiInlineGhostRef.current;
          const model = editor.getModel?.();
          const position = normalizeEditorPosition(editor.getPosition?.());
          if (!ghost || !model || !position) {
              return false;
          }
          const modelUri = String(model?.uri?.toString?.() || '');
          if (
              ghost.modelUri !== modelUri
              || ghost.position.lineNumber !== position.lineNumber
              || ghost.position.column !== position.column
              || !isInlineGhostSnapshotCurrent(model, position, ghost.snapshot)
          ) {
              clearAiInlineGhost();
              return false;
          }

          aiInlineGhostAcceptingRef.current = true;
          try {
              editor.pushUndoStop?.();
              const startOffset = typeof model.getOffsetAt === 'function'
                  ? Number(model.getOffsetAt(position))
                  : Number.NaN;
              editor.executeEdits?.('gonavi-ai-inline-sql-completion', [{
                  range: new monaco.Range(
                      position.lineNumber,
                      position.column,
                      position.lineNumber,
                      position.column,
                  ),
                  text: ghost.insertText,
                  forceMoveMarkers: true,
              }]);
              editor.pushUndoStop?.();
              syncQueryDraft(String(editor.getValue?.() ?? model.getValue?.() ?? ''));
              if (Number.isFinite(startOffset) && typeof model.getPositionAt === 'function') {
                  const nextPosition = normalizeEditorPosition(model.getPositionAt(startOffset + ghost.insertText.length));
                  if (nextPosition) {
                      editor.setPosition?.(nextPosition);
                  }
              }
          } finally {
              aiInlineGhostAcceptingRef.current = false;
              clearAiInlineGhost();
          }
          requestAiInlineGhost(0);
          return true;
      };

      const requestAiInlineGhost = (delayMs: number, focusEditor = false, manualTrigger = false) => {
          clearAiInlineGhost();
          if (aiInlineGhostAcceptingRef.current || editorRef.current !== editor) {
              return;
          }
          if (focusEditor) {
              editor.focus?.();
          }

          const model = editor.getModel?.();
          let position = normalizeEditorPosition(editor.getPosition?.());
          if (!model || !position) {
              return;
          }

          const modelUri = String(model?.uri?.toString?.() || '');
          if (modelUri && sharedActiveEditorModelUri && modelUri !== sharedActiveEditorModelUri) {
              return;
          }

          let editorSnapshot = buildInlineGhostEditorSnapshot(model, position);
          if (manualTrigger) {
              const normalizedState = recoverStrayManualSqlCompletionMarker(model, position, editorSnapshot);
              position = normalizedState.position;
              editorSnapshot = normalizedState.snapshot;
          }
          const intent = resolveQueryEditorInlineCompletionIntentDetails(editorSnapshot);
          const shouldUseInlineMemory = manualTrigger || intent.intent !== 'general_sql';
          if (shouldUseInlineMemory) {
              const memoryInsertText = resolveQueryEditorInlineMemoryInsertText({
                  editorSnapshot,
                  memoryEntries: inlineSqlMemoryEntries,
              });
              if (memoryInsertText.trim()) {
                  renderAiInlineGhost(model, position, memoryInsertText, editorSnapshot);
                  return;
              }
          }
          if (!shouldRequestQueryEditorInlineCompletion(editorSnapshot)) {
              return;
          }

          const requestId = ++aiInlineGhostRequestSeqRef.current;
          const runRequest = () => {
              if (aiInlineGhostTimerRef.current !== null) {
                  aiInlineGhostTimerRef.current = null;
              }
          void (async () => {
                  if (
                      requestId !== aiInlineGhostRequestSeqRef.current
                      || editorRef.current !== editor
                  ) {
                      return;
                  }
                  try {
                      await ensureQueryEditorAiContextMetadata(editorSnapshot);
                      const insertText = await requestQueryEditorInlineCompletion({
                          service: getQueryEditorAiService(),
                          aiContext: buildQueryEditorAiContext(),
                          editorSnapshot,
                      });
                      const currentPosition = normalizeEditorPosition(editor.getPosition?.());
                      if (
                          requestId !== aiInlineGhostRequestSeqRef.current
                          || !currentPosition
                          || currentPosition.lineNumber !== position.lineNumber
                          || currentPosition.column !== position.column
                          || !isInlineGhostSnapshotCurrent(model, currentPosition, editorSnapshot)
                          ) {
                          return;
                      }
                      if (!insertText.trim()) {
                          // Keep the manual AI action on the AI path; silently downgrading to plain suggest is misleading.
                          if (!manualTrigger && (intent.intent === 'table_name' || intent.intent === 'column_name')) {
                              const shouldTriggerStructuredSuggest = shouldTriggerQueryEditorInlineObjectSuggestFallback({
                                  aiContext: buildQueryEditorAiContext(),
                                  editorSnapshot,
                              });
                              if (shouldTriggerStructuredSuggest) {
                                  triggerStructuredSqlSuggest('gonavi-ai-inline-auto', true);
                              }
                          }
                          return;
                      }
                      renderAiInlineGhost(model, position, insertText, editorSnapshot);
                  } catch (error) {
                      console.warn('GoNavi AI inline SQL ghost failed', error);
                  }
              })();
          };

          if (delayMs > 0) {
              aiInlineGhostTimerRef.current = setTimeout(runRequest, delayMs);
              return;
          }
          runRequest();
      };

      const scheduleAiInlineGhost = () => {
          requestAiInlineGhost(QUERY_EDITOR_AI_INLINE_DEBOUNCE_MS);
      };

      triggerAiInlineCompletionRef.current = () => {
          requestAiInlineGhost(0, true, true);
      };

      if (monaco?.KeyCode?.RightArrow) {
          editor.addCommand?.(
              monaco.KeyCode.RightArrow,
              () => {
                  acceptAiInlineGhost();
              },
              QUERY_EDITOR_AI_INLINE_CONTEXT_KEY,
          );
          editor.addCommand?.(
              monaco.KeyCode.RightArrow,
              () => {
                  void editor.getAction?.('editor.action.inlineSuggest.commit')?.run?.();
              },
              'inlineSuggestionVisible',
          );
      }

      const repositionAiInlineGhost = () => {
          const ghost = aiInlineGhostRef.current;
          const model = editor.getModel?.();
          if (!ghost || !model) {
              return;
          }
          const modelUri = String(model?.uri?.toString?.() || '');
          if (ghost.modelUri !== modelUri) {
              clearAiInlineGhost();
              return;
          }
          renderAiInlineGhost(model, ghost.position, ghost.insertText, ghost.snapshot);
      };

      const applyNavigationHoverStateAtPosition = (targetPosition: { lineNumber: number; column: number } | null) => {
          if (!ctrlMetaPressedRef.current) {
              clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
              editor.updateOptions?.({ mouseStyle: 'text' });
              setQueryEditorMouseCursor(editor, '');
              return;
          }
          if (!targetPosition) {
              clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
              editor.updateOptions?.({ mouseStyle: 'text' });
              setQueryEditorMouseCursor(editor, '');
              return;
          }
          const model = editor.getModel?.();
          const lineContent = String(model?.getLineContent?.(targetPosition.lineNumber) || '');
          const decorations = resolveQueryEditorNavigationDecorations(
              lineContent,
              targetPosition.column,
              currentDbRef.current,
              visibleDbsRef.current,
              tablesRef.current,
              viewsRef.current,
              materializedViewsRef.current,
              triggersRef.current,
              routinesRef.current,
              sequencesRef.current,
              packagesRef.current,
              primaryShortcutModifierLabel,
          );
          if (decorations.length === 0) {
              clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
              editor.updateOptions?.({ mouseStyle: 'text' });
              setQueryEditorMouseCursor(editor, '');
              return;
          }
          linkDecorationIdsRef.current = editor.deltaDecorations(
              linkDecorationIdsRef.current,
              decorations.map((item) => ({
                  range: new monaco.Range(
                      targetPosition.lineNumber,
                      item.startColumn,
                      targetPosition.lineNumber,
                      item.endColumn,
                  ),
                  options: {
                      inlineClassName: 'gonavi-query-editor-link-hint',
                  },
              })),
          );
          setQueryEditorMouseCursor(editor, 'pointer');
      };

      const applyNavigationHoverState = (event: any) => {
          const targetPosition = normalizeEditorPosition(event?.target?.position);
          lastHoverTargetPositionRef.current = targetPosition;
          applyNavigationHoverStateAtPosition(targetPosition);
      };

      const syncModifierState = (keyboardEvent?: KeyboardEvent | MouseEvent | null) => {
          const wasPressed = ctrlMetaPressedRef.current;
          const isKeyboardLikeEvent = keyboardEvent
              && typeof keyboardEvent === 'object'
              && ('key' in keyboardEvent || 'code' in keyboardEvent || 'repeat' in keyboardEvent);
          if (isKeyboardLikeEvent && isImeComposingKeyEvent(keyboardEvent as KeyboardEvent)) {
              return;
          }
          const keyboardEventType = isKeyboardLikeEvent ? String((keyboardEvent as KeyboardEvent).type || '').toLowerCase() : '';
          const keyboardKey = isKeyboardLikeEvent ? String((keyboardEvent as KeyboardEvent).key || '').trim().toLowerCase() : '';
          const keyboardCode = isKeyboardLikeEvent ? String((keyboardEvent as KeyboardEvent).code || '').trim().toLowerCase() : '';
          const isModifierKeyDown = isKeyboardLikeEvent
              && keyboardEventType !== 'keyup'
              && (
                  keyboardKey === 'control'
                  || keyboardKey === 'ctrl'
                  || keyboardKey === 'meta'
                  || keyboardKey === 'os'
                  || keyboardKey === 'command'
                  || keyboardCode.startsWith('control')
                  || keyboardCode.startsWith('meta')
                  || keyboardCode.startsWith('os')
              );
          const eventHasModifierFlag = hasQueryEditorCtrlMetaModifier(keyboardEvent);
          const nextPressed = isKeyboardLikeEvent
              ? !!(eventHasModifierFlag || isModifierKeyDown)
              : !!(eventHasModifierFlag || wasPressed);
          ctrlMetaPressedRef.current = nextPressed;
          if (!nextPressed && !wasPressed) {
              return;
          }
          if (!nextPressed) {
              clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
              editor.updateOptions?.({ mouseStyle: 'text' });
              setQueryEditorMouseCursor(editor, '');
              return;
          }
          if (!wasPressed || isKeyboardLikeEvent) {
              const keyboardFallbackPosition = isKeyboardLikeEvent
                  ? normalizeEditorPosition(editor.getPosition?.()) || lastEditorCursorPositionRef.current
                  : null;
              applyNavigationHoverStateAtPosition(lastHoverTargetPositionRef.current || keyboardFallbackPosition);
          }
      };
      const handleWindowBlur = () => {
          ctrlMetaPressedRef.current = false;
          clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
          editor.updateOptions?.({ mouseStyle: 'text' });
          setQueryEditorMouseCursor(editor, '');
      };
      const editorDomNode = editor.getDomNode?.();
      const clearImeCompositionFallbackTimer = () => {
          if (imeCompositionFallbackTimerRef.current !== null) {
              clearTimeout(imeCompositionFallbackTimerRef.current);
              imeCompositionFallbackTimerRef.current = null;
          }
      };
      const getEditorText = () => String(
          editor.getValue?.()
          ?? editor.getModel?.()?.getValue?.()
          ?? '',
      );
      const buildImeFallbackRange = (snapshot: NonNullable<typeof imeCompositionFallbackRef.current>) => {
          const selection = snapshot.selectionBefore;
          const startFromSelection = typeof selection?.getStartPosition === 'function'
              ? normalizeEditorPosition(selection.getStartPosition())
              : null;
          const endFromSelection = typeof selection?.getEndPosition === 'function'
              ? normalizeEditorPosition(selection.getEndPosition())
              : null;
          const startPosition = startFromSelection || normalizeEditorPosition({
              lineNumber: selection?.startLineNumber ?? selection?.selectionStartLineNumber,
              column: selection?.startColumn ?? selection?.selectionStartColumn,
          }) || snapshot.positionBefore || lastEditorCursorPositionRef.current || { lineNumber: 1, column: 1 };
          const endPosition = endFromSelection || normalizeEditorPosition({
              lineNumber: selection?.endLineNumber ?? selection?.positionLineNumber,
              column: selection?.endColumn ?? selection?.positionColumn,
          }) || startPosition;
          return new monaco.Range(
              startPosition.lineNumber,
              startPosition.column,
              endPosition.lineNumber,
              endPosition.column,
          );
      };
      const handleImeCompositionStart = () => {
          clearImeCompositionFallbackTimer();
          imeCompositionFallbackRef.current = {
              editor,
              valueBefore: getEditorText(),
              selectionBefore: editor.getSelection?.() || null,
              positionBefore: normalizeEditorPosition(editor.getPosition?.()) || lastEditorCursorPositionRef.current || null,
              committedText: '',
          };
      };
      const handleImeBeforeInput = (rawEvent: Event) => {
          const snapshot = imeCompositionFallbackRef.current;
          if (!snapshot || snapshot.editor !== editor) {
              return;
          }
          const inputEvent = rawEvent as InputEvent;
          const nextText = String(inputEvent.data ?? '');
          if (nextText && (inputEvent.isComposing || String(inputEvent.inputType || '').includes('Composition'))) {
              snapshot.committedText = nextText;
          }
      };
      const handleImeCompositionEnd = (rawEvent: Event) => {
          const snapshot = imeCompositionFallbackRef.current;
          imeCompositionFallbackRef.current = null;
          const committedText = String((rawEvent as CompositionEvent).data ?? '') || snapshot?.committedText || '';
          if (!committedText || !snapshot || snapshot.editor !== editor) {
              return;
          }

          const fallbackRange = buildImeFallbackRange(snapshot);
          clearImeCompositionFallbackTimer();
          imeCompositionFallbackTimerRef.current = setTimeout(() => {
              imeCompositionFallbackTimerRef.current = null;
              if (editorRef.current !== editor) {
                  return;
              }
              const currentValue = getEditorText();
              if (currentValue !== snapshot.valueBefore) {
                  syncQueryDraft(currentValue);
                  return;
              }

              editor.executeEdits?.('gonavi-ime-composition-fallback', [{
                  range: fallbackRange,
                  text: committedText,
                  forceMoveMarkers: true,
              }]);
              const nextValue = getEditorText();
              syncQueryDraft(nextValue);

              const model = editor.getModel?.();
              const startOffset = Number(model?.getOffsetAt?.({
                  lineNumber: fallbackRange.startLineNumber,
                  column: fallbackRange.startColumn,
              }));
              const nextPosition = Number.isFinite(startOffset)
                  ? normalizeEditorPosition(model?.getPositionAt?.(startOffset + committedText.length))
                  : null;
              if (nextPosition) {
                  editor.setPosition?.(nextPosition);
              }
          }, 0);
      };
      const handleEditorDragOver = (rawEvent: Event) => {
          const event = rawEvent as DragEvent;
          if (!hasSidebarSqlEditorDragPayload(event.dataTransfer)) return;
          event.preventDefault();
          event.stopPropagation();
          if (event.dataTransfer) {
              event.dataTransfer.dropEffect = 'copy';
          }
      };
      const handleEditorDrop = (rawEvent: Event) => {
          handleSidebarObjectDrop(rawEvent as DragEvent);
      };

      // 应用透明主题（主题由 MonacoEditor 包装组件按需注册）
      monaco.editor.setTheme(darkMode ? 'transparent-dark' : 'transparent-light');

      objectHoverActionRef.current?.dispose?.();
      const showObjectInfoKeybinding = monaco.KeyMod?.CtrlCmd && monaco.KeyCode?.KeyQ
          ? [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyQ]
          : undefined;
      objectHoverActionRef.current = editor.addAction({
          id: 'gonavi.queryEditor.showObjectInfo',
          label: buildQueryEditorMonacoActionLabel('query_editor.action.show_object_info'),
          keybindings: showObjectInfoKeybinding,
          run: () => {
              const preferredPosition = lastHoverTargetPositionRef.current || editor.getPosition?.();
              const shown = showObjectInfoAtPosition(preferredPosition);
              if (!shown) {
                  void message.info({
                      key: 'gonavi-query-editor-object-info-miss',
                      content: translate('query_editor.message.object_info_target_not_found'),
                  });
              }
          },
      });

      editor.onDidChangeCursorPosition?.((event: any) => {
          const position = normalizeEditorPosition(event?.position);
          if (position) {
              lastEditorCursorPositionRef.current = position;
          }
          const ghost = aiInlineGhostRef.current;
          if (
              ghost
              && (!position
                  || ghost.position.lineNumber !== position.lineNumber
                  || ghost.position.column !== position.column)
          ) {
              clearAiInlineGhost();
          }
      });

      const recoverTriggerSqlAiCompletionFallback = (event: any): boolean => {
          if (triggerSqlAiCompletionFallbackApplyingRef.current) {
              return true;
          }

          const pending = triggerSqlAiCompletionFallbackRef.current;
          const altGestureAge = Date.now() - Number(triggerSqlAiCompletionAltGestureAtRef.current || 0);
          const hasRecentAltGesture = altGestureAge >= 0 && altGestureAge <= 1200;
          const changes = Array.isArray(event?.changes) ? event.changes : [];
          const backslashChange = changes.find((change: any) => String(change?.text ?? '') === '\\');
          if (!backslashChange) {
              if (pending && (Date.now() - pending.observedAt) > 1200) {
                  triggerSqlAiCompletionFallbackRef.current = null;
              }
              return false;
          }

          const model = editor.getModel?.();
          if (!model || typeof model.getOffsetAt !== 'function' || typeof model.getValue !== 'function') {
              return false;
          }

          let markerOffset = Number.NaN;
          let startPosition = normalizeEditorPosition(backslashChange?.range
              ? {
                  lineNumber: Number(backslashChange.range.startLineNumber || 1),
                  column: Number(backslashChange.range.startColumn || 1),
              }
              : null);
          let endPosition = normalizeEditorPosition(backslashChange?.range
              ? {
                  lineNumber: Number(backslashChange.range.endLineNumber || 1),
                  column: Number(backslashChange.range.endColumn || 1),
              }
              : null);

          const rangeOffset = Number(backslashChange?.rangeOffset);
          if (Number.isFinite(rangeOffset)) {
              markerOffset = rangeOffset;
          } else if (startPosition) {
              markerOffset = Number(model.getOffsetAt(startPosition));
          } else {
              const currentPosition = normalizeEditorPosition(editor.getPosition?.());
              const currentOffset = currentPosition ? Number(model.getOffsetAt(currentPosition)) : Number.NaN;
              if (Number.isFinite(currentOffset) && currentOffset > 0) {
                  markerOffset = currentOffset - 1;
              }
          }

          if (!Number.isFinite(markerOffset) || markerOffset < 0) {
              return false;
          }
          const currentModelText = String(model?.getValue?.() ?? '');
          if (currentModelText.slice(markerOffset, markerOffset + 1) !== '\\') {
              return false;
          }
          startPosition = normalizeEditorPosition(model?.getPositionAt?.(markerOffset));
          endPosition = normalizeEditorPosition(model?.getPositionAt?.(markerOffset + 1));
          const fallbackSnapshot = buildInlineGhostEditorSnapshotFromInsertedTextRemoval(
              currentModelText,
              markerOffset,
              1,
          );
          const fallbackIntent = fallbackSnapshot
              ? resolveQueryEditorInlineCompletionIntentDetails(fallbackSnapshot)
              : null;
          const hasStructuredSqlCompletionContext = fallbackIntent?.intent === 'table_name'
              || fallbackIntent?.intent === 'column_name';
          if (!pending && !hasRecentAltGesture && !hasStructuredSqlCompletionContext) {
              return false;
          }
          if (pending && (Date.now() - pending.observedAt) > 1200) {
              triggerSqlAiCompletionFallbackRef.current = null;
              if (!hasRecentAltGesture && !hasStructuredSqlCompletionContext) {
                  return false;
              }
          }

          if (!startPosition || !endPosition) {
              return false;
          }

          triggerSqlAiCompletionFallbackRef.current = null;
          triggerSqlAiCompletionFallbackApplyingRef.current = true;
          try {
              editor.executeEdits?.('gonavi-trigger-sql-ai-completion-fallback', [{
                  range: new monaco.Range(
                      startPosition.lineNumber,
                      startPosition.column,
                      endPosition.lineNumber,
                      endPosition.column,
                  ),
                  text: '',
                  forceMoveMarkers: true,
              }]);
              editor.setPosition?.(startPosition);
              syncQueryDraft(getEditorText());
          } finally {
              triggerSqlAiCompletionFallbackApplyingRef.current = false;
          }
          triggerAiInlineCompletionRef.current?.();
          return true;
      };

      editor.onDidChangeModelContent?.((event: any) => {
          if (recoverTriggerSqlAiCompletionFallback(event)) {
              return;
          }
          const hasSlashCommandMarker = Array.isArray(event?.changes)
              && event.changes.some((change: any) => /__AI_\w+__/.test(String(change?.text || '')));
          if (hasSlashCommandMarker) {
              refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
          }
          // SQL 文本变更后，按引用库集合防抖触发跨库元数据拉取（db.table / schema.table / db.schema.table）
          if (sqlReferencedMetadataTimerRef.current !== null) {
              window.clearTimeout(sqlReferencedMetadataTimerRef.current);
          }
          sqlReferencedMetadataTimerRef.current = window.setTimeout(() => {
              sqlReferencedMetadataTimerRef.current = null;
              if (editorRef.current !== editor) {
                  return;
              }
              const modelText = String(editor.getModel?.()?.getValue?.() || '');
              const referencedDbs = collectQueryEditorReferencedDatabaseNames(
                  modelText,
                  currentDbRef.current || '',
                  visibleDbsRef.current,
              );
              const nextKey = [
                  String(currentConnectionIdRef.current || '').trim(),
                  ...referencedDbs.map((dbName) => String(dbName || '').toLowerCase()).sort(),
              ].join('\u0000');
              if (nextKey === lastSqlReferencedMetadataKeyRef.current) {
                  // 库集合未变：仍刷新装饰（可能表列表已异步到位）
                  refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
                  return;
              }
              lastSqlReferencedMetadataKeyRef.current = nextKey;
              setSqlReferencedMetadataKey(nextKey);
          }, 450);
          const acceptedCurrentAiGhost = !aiInlineGhostAcceptingRef.current
              && didModelContentAcceptCurrentAiInlineGhost(event);
          if (acceptedCurrentAiGhost) {
              clearAiInlineGhost(false);
              window.setTimeout(() => {
                  if (editorRef.current !== editor) {
                      return;
                  }
                  requestAiInlineGhost(0);
              }, 0);
              return;
          }
          if (!aiInlineGhostAcceptingRef.current) {
              scheduleAiInlineGhost();
          }
      });

      // 滚动/布局事件可达每帧多次，rAF 合并避免高频 DOM 重排。
      let repositionAiInlineGhostRafId: number | null = null;
      const scheduleRepositionAiInlineGhost = () => {
          if (!aiInlineGhostRef.current || repositionAiInlineGhostRafId !== null) {
              return;
          }
          repositionAiInlineGhostRafId = window.requestAnimationFrame(() => {
              repositionAiInlineGhostRafId = null;
              if (editorRef.current !== editor) {
                  return;
              }
              repositionAiInlineGhost();
          });
      };

      editor.onDidScrollChange?.(() => {
          scheduleRepositionAiInlineGhost();
      });
      editor.onDidLayoutChange?.(() => {
          scheduleRepositionAiInlineGhost();
      });

      editor.onMouseMove?.((event: any) => {
          syncModifierState(event?.event || null);
          applyNavigationHoverState(event);
      });
      editor.onMouseLeave?.(() => {
          lastHoverTargetPositionRef.current = null;
          clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
          editor.updateOptions?.({ mouseStyle: 'text' });
          setQueryEditorMouseCursor(editor, '');
      });

      window.addEventListener('keydown', syncModifierState);
      window.addEventListener('keyup', syncModifierState);
      window.addEventListener('blur', handleWindowBlur);
      editorDomNode?.addEventListener('beforeinput', handleImeBeforeInput, true);
      editorDomNode?.addEventListener('compositionstart', handleImeCompositionStart, true);
      editorDomNode?.addEventListener('compositionend', handleImeCompositionEnd, true);
      editorDomNode?.addEventListener('dragover', handleEditorDragOver, true);
      editorDomNode?.addEventListener('drop', handleEditorDrop, true);

      editor.onMouseDown?.((event: any) => {
          const browserEvent = event?.event;
          const targetPosition = normalizeEditorPosition(event?.target?.position);
          if (!browserEvent || !targetPosition) {
              return;
          }
          if (!isQueryEditorPrimaryMouseButton(browserEvent)) {
              return;
          }
          if (!hasQueryEditorCtrlMetaModifier(browserEvent) && !ctrlMetaPressedRef.current) {
              return;
          }

          const model = editor.getModel?.();
          const lineContent = String(model?.getLineContent?.(targetPosition.lineNumber) || '');
          const navigationTarget = resolveQueryEditorNavigationTarget(
              lineContent,
              targetPosition.column,
              currentDbRef.current,
              visibleDbsRef.current,
              tablesRef.current,
              viewsRef.current,
              materializedViewsRef.current,
              triggersRef.current,
              routinesRef.current,
              sequencesRef.current,
              packagesRef.current,
          );
          if (!navigationTarget) {
              return;
          }

          browserEvent.preventDefault?.();
          browserEvent.stopPropagation?.();

          const connectionId = String(currentConnectionIdRef.current || '').trim();
          if (!connectionId) {
              return;
          }

          if (navigationTarget.type === 'database') {
              const nextDbName = String(navigationTarget.dbName || '').trim();
              if (!nextDbName) {
                  return;
              }
              setCurrentDb(nextDbName);
              currentDbRef.current = nextDbName;
              setActiveContext({ connectionId, dbName: nextDbName });
              return;
          }

          const targetDbName = String(navigationTarget.dbName || '').trim();
          if (!targetDbName) {
              return;
          }

          if (navigationTarget.type === 'table') {
              const targetTableName = String(navigationTarget.tableName || '').trim();
              if (!targetTableName) return;
              addTab({
                  id: `${connectionId}-${targetDbName}-table-${targetTableName}`,
                  title: targetTableName,
                  type: 'table',
                  connectionId,
                  dbName: targetDbName,
                  tableName: targetTableName,
                  initialViewMode: 'fields',
                  initialViewModeRequestId: String(Date.now()),
                  objectType: 'table',
                  returnToTabId: activeTabId || undefined,
              });
              return;
          }

          if (navigationTarget.type === 'view' || navigationTarget.type === 'materialized-view') {
              void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
              return;
          }

          if (navigationTarget.type === 'trigger') {
              void openTriggerObjectEditTab(navigationTarget, connectionId, targetDbName);
              return;
          }

          if (navigationTarget.type === 'sequence') {
              void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
              return;
          }

          if (navigationTarget.type === 'package') {
              void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
              return;
          }

          void openRoutineObjectEditTab(navigationTarget, connectionId, targetDbName);
      });

      editor.onDidDispose?.(() => {
          clearQueryEditorLinkDecorations(editor, linkDecorationIdsRef);
          clearQueryEditorObjectDecorations(editor, objectDecorationIdsRef);
          setQueryEditorMouseCursor(editor, '');
          objectHoverActionRef.current?.dispose?.();
          objectHoverActionRef.current = null;
          triggerSqlAiCompletionActionRef.current?.dispose?.();
          triggerSqlAiCompletionActionRef.current = null;
          macFindWithSelectionGuardActionRef.current?.dispose?.();
          macFindWithSelectionGuardActionRef.current = null;
          triggerSqlAiCompletionKeydownDisposableRef.current?.dispose?.();
          triggerSqlAiCompletionKeydownDisposableRef.current = null;
          triggerAiInlineCompletionRef.current = null;
          const disposedModelUri = String(editor.getModel?.()?.uri?.toString?.() || '');
          if (disposedModelUri && sharedActiveEditorModelUri === disposedModelUri) {
              sharedActiveEditorModelUri = '';
          }
          disposeQueryEditorAiContextMenuActions();
          window.removeEventListener('keydown', syncModifierState);
          window.removeEventListener('keyup', syncModifierState);
          window.removeEventListener('blur', handleWindowBlur);
          clearImeCompositionFallbackTimer();
          editorDomNode?.removeEventListener('beforeinput', handleImeBeforeInput, true);
          editorDomNode?.removeEventListener('compositionstart', handleImeCompositionStart, true);
          editorDomNode?.removeEventListener('compositionend', handleImeCompositionEnd, true);
          editorDomNode?.removeEventListener('dragover', handleEditorDragOver, true);
          editorDomNode?.removeEventListener('drop', handleEditorDrop, true);
      });

      refreshObjectDecorations();

      // 注册 AI 右键菜单操作
      registerQueryEditorAiContextMenuActions(editor);
      registerInsertSqlSnippetContextMenuAction(editor);
      registerTriggerSqlAiCompletionAction(editor, monaco);

      // Register runQuery shortcut inside Monaco so it overrides Monaco's default keybinding
      const runBinding = runQueryShortcutBinding;
      if (runBinding?.enabled && runBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              runBinding.combo, monaco.KeyMod, monaco.KeyCode
          );
          if (keyBinding) {
              runQueryActionRef.current = editor.addAction({
                  id: 'gonavi.runQuery',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: () => {
                      window.dispatchEvent(new CustomEvent('gonavi:run-active-query', {
                          detail: { requireSelection: true },
                      }));
                  },
              });
          }
      }

      const selectStatementBinding = selectCurrentStatementShortcutBinding;
      if (selectStatementBinding?.enabled && selectStatementBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              selectStatementBinding.combo, monaco.KeyMod, monaco.KeyCode
          );
          if (keyBinding) {
              selectCurrentStatementActionRef.current = editor.addAction({
                  id: 'gonavi.selectCurrentStatement',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.selectCurrentStatement.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: handleSelectCurrentStatement,
              });
          }
      }

      const macFindWithSelectionGuardKeyBinding = activeShortcutPlatform === 'mac'
          ? comboToMonacoKeyBinding(
              QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO, monaco.KeyMod, monaco.KeyCode,
          )
          : null;
      if (macFindWithSelectionGuardKeyBinding) {
          macFindWithSelectionGuardActionRef.current = editor.addAction({
              id: QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID,
              label: 'GoNavi: Suppress macOS Cmd+E Find with Selection',
              keybindings: [
                  macFindWithSelectionGuardKeyBinding.keyMod
                  | macFindWithSelectionGuardKeyBinding.keyCode,
              ],
              run: () => {
                  if (
                      selectStatementBinding?.enabled
                      && normalizeShortcutCombo(selectStatementBinding.combo) === QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO
                  ) {
                      void handleSelectCurrentStatement();
                  }
              },
          });
      }

      const duplicateLineBinding = duplicateCurrentLineShortcutBinding;
      if (duplicateLineBinding?.enabled && duplicateLineBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              duplicateLineBinding.combo, monaco.KeyMod, monaco.KeyCode,
          );
          if (keyBinding) {
              duplicateCurrentLineActionRef.current = editor.addAction({
                  id: 'gonavi.duplicateCurrentLine',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.duplicateCurrentLine.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: handleDuplicateCurrentLine,
              });
          }
      }

      const saveBinding = saveQueryShortcutBinding;
      if (saveBinding?.enabled && saveBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              saveBinding.combo, monaco.KeyMod, monaco.KeyCode
          );
          if (keyBinding) {
              saveQueryActionRef.current = editor.addAction({
                  id: 'gonavi.saveQuery',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.saveQuery.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: () => {
                      window.dispatchEvent(new CustomEvent('gonavi:save-active-query'));
                  },
              });
          }
      }

      const findInEditorKeyBinding = comboToMonacoKeyBinding(
          findInEditorShortcutCombo, monaco.KeyMod, monaco.KeyCode
      );
      if (findInEditorKeyBinding) {
          findInEditorActionRef.current = editor.addAction({
              id: 'gonavi.findInEditor',
              label: buildQueryEditorMonacoActionLabel('query_editor.action.find_in_editor'),
              keybindings: [findInEditorKeyBinding.keyMod | findInEditorKeyBinding.keyCode],
              run: () => {
                  window.dispatchEvent(new CustomEvent('gonavi:find-active-query'));
              },
          });
      }

      const formatBinding = formatSqlShortcutBinding;
      if (formatBinding?.enabled && formatBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              formatBinding.combo, monaco.KeyMod, monaco.KeyCode
          );
          if (keyBinding) {
              formatSqlActionRef.current = editor.addAction({
                  id: 'gonavi.formatSql',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.formatSql.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: () => {
                      window.dispatchEvent(new CustomEvent('gonavi:format-active-query'));
                  },
              });
          }
      }

      // 注册 / 斜杠命令 AI 快捷补全
      refreshQueryEditorSlashCommandDefs();
      const toggleResultsBinding = toggleQueryResultsPanelShortcutBinding;
      if (toggleResultsBinding?.enabled && toggleResultsBinding.combo) {
          const keyBinding = comboToMonacoKeyBinding(
              toggleResultsBinding.combo, monaco.KeyMod, monaco.KeyCode
          );
          if (keyBinding) {
              toggleQueryResultsPanelActionRef.current = editor.addAction({
                  id: 'gonavi.toggleQueryResultsPanel',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.toggleQueryResultsPanel.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: toggleResultPanelVisibility,
              });
          }
      }

      // HMR 重载或测试重置时，以全局状态为准，避免本地闭包状态和 provider 列表不同步。
      sqlCompletionRegistered = Boolean(_g.__gonaviSqlCompletionState.registered);
      sqlCompletionDisposables = _g.__gonaviSqlCompletionState.disposables;
      const shouldRegisterSqlCompletion = !sqlCompletionRegistered
          || _g.__gonaviSqlCompletionState.version !== SQL_COMPLETION_PROVIDER_VERSION;

      // HMR 重载时释放旧注册避免补全项重复
      if (shouldRegisterSqlCompletion) {
      sqlCompletionRegistered = true;
      _g.__gonaviSqlCompletionState.registered = true;
      _g.__gonaviSqlCompletionState.version = SQL_COMPLETION_PROVIDER_VERSION;
      sqlCompletionDisposables.forEach((d: any) => d?.dispose?.());
      sqlCompletionDisposables.length = 0;
      sqlCompletionDisposables.push(monaco.languages.registerHoverProvider('sql', {
          provideHover: (model: any, position: any) => {
              const normalizedPosition = normalizeEditorPosition(position);
              if (!normalizedPosition) {
                  return null;
              }
              const lineContent = String(model?.getLineContent?.(normalizedPosition.lineNumber) || '');
              const resolveText = getQueryEditorObjectResolveText(model, lineContent);
              const hoverTarget = resolveQueryEditorHoverTarget(
                  resolveText,
                  lineContent,
                  normalizedPosition.column,
                  sharedCurrentDb,
                  sharedVisibleDbs,
                  sharedTablesData,
                  sharedAllColumnsData,
                  sharedViewsData,
                  sharedMaterializedViewsData,
                  sharedTriggersData,
                  sharedRoutinesData,
                  sharedSequencesData,
                  sharedPackagesData,
              );
              if (!hoverTarget) {
                  return null;
              }
              return {
                  range: new monaco.Range(
                      normalizedPosition.lineNumber,
                      hoverTarget.range.startColumn,
                      normalizedPosition.lineNumber,
                      hoverTarget.range.endColumn,
                  ),
                  contents: [{ value: buildQueryEditorHoverMarkdown(hoverTarget) }],
              };
          },
      }));
      sqlCompletionDisposables.push(monaco.languages.registerCompletionItemProvider('sql', {
          triggerCharacters: ['.'],
          provideCompletionItems: async (model: any, position: any, _context?: any, token?: { isCancellationRequested?: boolean }) => {
              if (isSqlCompletionRequestCancelled(token)) {
                  return createEmptySqlCompletionResult();
              }
              const word = model.getWordUntilPosition(position);
              const range = {
                  startLineNumber: position.lineNumber,
                  endLineNumber: position.lineNumber,
                  startColumn: word.startColumn,
                  endColumn: word.endColumn,
              };
              const activeConnection = sharedConnections.find(c => c.id === sharedCurrentConnectionId);
              const activeDialect = resolveSqlDialect(
                  String(activeConnection?.config?.type || ''),
                  String(activeConnection?.config?.driver || ''),
                  { oceanBaseProtocol: activeConnection?.config?.oceanBaseProtocol },
              );
              const shouldQuoteCompletionIdentifiers = isPostgresSchemaDialect(activeDialect);
              const quoteCompletionPart = (ident: string) => {
                  const raw = String(ident || '').trim();
                  if (!raw) return raw;
                  return shouldQuoteCompletionIdentifiers ? quoteIdentPart(activeDialect, raw) : raw;
              };
              const quoteCompletionPath = (ident: string) => {
                  const raw = String(ident || '').trim();
                  if (!raw) return raw;
                  return shouldQuoteCompletionIdentifiers ? quoteQualifiedIdent(activeDialect, raw) : raw;
              };
              const getActiveCompletionDbName = () => String(sharedCurrentDb || currentDbRef.current || currentDb || tab.dbName || '').trim();
              const dialectKeywords = resolveSqlKeywords(activeDialect);
              const dialectFunctions = resolveSqlFunctions(activeDialect);

              const stripQuotes = stripCompletionIdentifierQuotes;
              const normalizeQualifiedName = normalizeCompletionQualifiedName;
              const splitSchemaAndTable = splitCompletionSchemaAndTable;
              const buildDbQualifiedTableSuggestionMeta = (dbName: string, tableName: string) => {
                  const rawDbName = String(dbName || '').trim();
                  const rawTableName = String(tableName || '').trim();
                  const parsed = splitSchemaAndTable(rawTableName);
                  const schemaMatchesDb = !!parsed.schema
                      && !!parsed.table
                      && parsed.schema.toLowerCase() === rawDbName.toLowerCase();
                  const displayName = schemaMatchesDb ? parsed.table : rawTableName;
                  const insertText = schemaMatchesDb
                      ? quoteCompletionPart(parsed.table)
                      : quoteCompletionPath(rawTableName);
                  const dbQualifiedLabel = rawDbName
                      ? `${rawDbName}.${displayName || rawTableName}`
                      : (displayName || rawTableName);
                  return {
                      displayName: displayName || rawTableName,
                      insertText,
                      dbQualifiedLabel,
                  };
              };
              const normalizeRoutineType = (routineType: string) => (
                  String(routineType || '').trim().toUpperCase().includes('PROC') ? 'PROCEDURE' : 'FUNCTION'
              );
              const getRoutineTypeLabel = (routineType: string) => (
                  normalizeRoutineType(routineType) === 'PROCEDURE'
                      ? translate('sidebar.object.procedure')
                      : translate('sidebar.object.function')
              );
              const buildRoutineSuggestionMeta = (routine: CompletionRoutineMeta) => {
                  const rawDbName = String(routine.dbName || '').trim();
                  const rawRoutineName = String(routine.routineName || '').trim();
                  const parsed = splitSchemaAndTable(rawRoutineName);
                  const schemaName = String(routine.schemaName || parsed.schema || '').trim();
                  const objectName = String(parsed.table || rawRoutineName).trim();
                  const schemaMatchesDb = !!schemaName
                      && !!rawDbName
                      && schemaName.toLowerCase() === rawDbName.toLowerCase();
                  const isCurrentDb = rawDbName.toLowerCase() === getActiveCompletionDbName().toLowerCase();
                  const displayName = isCurrentDb && schemaMatchesDb
                      ? objectName
                      : (parsed.schema ? rawRoutineName : objectName);
                  const dbQualifiedLabel = rawDbName && !isCurrentDb
                      ? `${rawDbName}.${displayName}`
                      : displayName;
                  const insertName = rawDbName && !isCurrentDb
                      ? dbQualifiedLabel
                      : displayName;
                  return {
                      displayName,
                      dbQualifiedLabel,
                      insertText: `${quoteCompletionPath(insertName)}($0)`,
                      objectName,
                      schemaName,
                      routineType: normalizeRoutineType(routine.routineType),
                  };
              };

              const buildConnConfig = () => {
                  const connId = sharedCurrentConnectionId;
                  const conn = sharedConnections.find(c => c.id === connId);
                  if (!conn) return null;
                  return {
                      ...conn.config,
                      port: Number(conn.config.port),
                      password: conn.config.password || "",
                      database: conn.config.database || "",
                      useSSH: conn.config.useSSH || false,
                      ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
                  };
              };

              const getLazyTablesByDB = async (dbName: string) => {
                  const connId = sharedCurrentConnectionId;
                  if (!connId || !dbName) return [] as CompletionTableMeta[];
                  const key = `${connId}|${dbName}`;
                  if (sharedLazyTablesCache[key]) {
                      return sharedLazyTablesCache[key];
                  }
                  if (sharedLazyTablesInFlight[key]) {
                      return sharedLazyTablesInFlight[key];
                  }

                  const config = buildConnConfig();
                  if (!config) return [] as CompletionTableMeta[];
                  const conn = sharedConnections.find(c => c.id === connId);

                  sharedLazyTablesInFlight[key] = Promise.all([
                      fetchCompletionTableCommentMap(config, dbName, normalizeMetadataDialect(conn)),
                      DBGetTables(buildRpcConnectionConfig(config) as any, dbName),
                  ])
                      .then(([tableComments, res]) => {
                          const tables = res?.success && Array.isArray(res.data)
                              ? res.data
                                  .map((row: any) => buildCompletionTableMeta(dbName, row, tableComments))
                                  .filter((table): table is CompletionTableMeta => !!table)
                              : [];
                          sharedLazyTablesCache[key] = tables;
                          if (tables.length > 0) {
                              const lazyTableByKey = new Map(tables.map((table) => [
                                  `${table.dbName.toLowerCase()}.${table.tableName.toLowerCase()}`,
                                  table,
                              ]));
                              const existingKeys = new Set<string>();
                              let changed = false;
                              const nextSharedTables = sharedTablesData.map((table) => {
                                  const tableKey = `${table.dbName.toLowerCase()}.${table.tableName.toLowerCase()}`;
                                  existingKeys.add(tableKey);
                                  const lazyTable = lazyTableByKey.get(tableKey);
                                  if (lazyTable?.comment && lazyTable.comment !== table.comment) {
                                      changed = true;
                                      return { ...table, comment: lazyTable.comment };
                                  }
                                  return table;
                              });
                              const missingTables = tables.filter((table) => !existingKeys.has(`${table.dbName.toLowerCase()}.${table.tableName.toLowerCase()}`));
                              if (missingTables.length > 0) {
                                  changed = true;
                                  nextSharedTables.push(...missingTables);
                              }
                              if (changed) {
                                  sharedTablesData = nextSharedTables;
                              }
                          }
                          return tables;
                      })
                      .catch(() => [])
                      .finally(() => {
                          delete sharedLazyTablesInFlight[key];
                      });
                  return sharedLazyTablesInFlight[key];
              };

              const toCompletionColumns = (
                  columns: ColumnDefinition[],
                  dbName: string,
                  tableName: string,
              ): CompletionColumnMeta[] => columns
                  .map((column) => ({
                      dbName,
                      tableName,
                      name: getColumnDefinitionName(column),
                      type: getColumnDefinitionType(column),
                      comment: getColumnDefinitionComment(column),
                  }))
                  .filter((column) => !!column.name);

              const findPreloadedColumns = (dbName: string, tableName: string) =>
                  findSharedPreloadedColumns(dbName, tableName);

              const mergeSharedCompletionColumns = (columns: CompletionColumnMeta[]) => {
                  if (columns.length === 0) return;
                  const existingKeys = new Set(sharedAllColumnsData.map((column) => [
                      String(column.dbName || '').toLowerCase(),
                      String(column.tableName || '').toLowerCase(),
                      String(column.name || '').toLowerCase(),
                  ].join('\u0000')));
                  const missing = columns.filter((column) => {
                      const key = [
                          String(column.dbName || '').toLowerCase(),
                          String(column.tableName || '').toLowerCase(),
                          String(column.name || '').toLowerCase(),
                      ].join('\u0000');
                      if (existingKeys.has(key)) return false;
                      existingKeys.add(key);
                      return true;
                  });
                  if (missing.length > 0) {
                      sharedAllColumnsData = [...sharedAllColumnsData, ...missing];
                  }
              };

              const getCompletionColumnsByTable = async (dbName: string, tableIdent: string) => {
                  const connId = sharedCurrentConnectionId;
                  const targetDb = String(dbName || '').trim();
                  const targetTable = String(tableIdent || '').trim();
                  if (!connId || !targetDb || !targetTable) return [] as CompletionColumnMeta[];

                  const preloaded = findPreloadedColumns(targetDb, targetTable);
                  if (preloaded.length > 0) {
                      return preloaded;
                  }

                  const key = `${connId}|${targetDb}|${targetTable}`;
                  const cached = sharedColumnsCacheData[key] as ColumnDefinition[] | undefined;
                  if (cached) {
                      const cachedColumns = toCompletionColumns(cached, targetDb, targetTable);
                      mergeSharedCompletionColumns(cachedColumns);
                      return cachedColumns;
                  }

                  const config = buildConnConfig();
                  if (!config) return [] as CompletionColumnMeta[];

                  const res = await DBGetColumns(buildRpcConnectionConfig(config) as any, targetDb, targetTable);
                  if (res?.success && Array.isArray(res.data)) {
                      const cols = res.data as ColumnDefinition[];
                      sharedColumnsCacheData[key] = cols;
                      const completionColumns = toCompletionColumns(cols, targetDb, targetTable);
                      mergeSharedCompletionColumns(completionColumns);
                      return completionColumns;
                  }
                  return [] as CompletionColumnMeta[];
              };

              const fullText = model.getValue();
              const cursorOffset = getNormalizedOffsetAtPosition(fullText, {
                  lineNumber: Number(position?.lineNumber || 1),
                  column: Number(position?.column || 1),
              });
              const currentStatementRange = resolveCurrentSqlStatementRange(fullText, cursorOffset);

              // 获取当前行光标前的内容
              const linePrefix = model.getLineContent(position.lineNumber).slice(0, position.column - 1);
              const currentStatementPrefix = currentStatementRange
                  ? fullText.slice(currentStatementRange.start, cursorOffset)
                  : fullText.slice(0, cursorOffset);
              const completionScopeText = currentStatementPrefix || linePrefix;
              const currentStatementText = currentStatementRange?.text || '';
              const completionReferenceText = currentStatementText || completionScopeText;

              // 0) 三段式 db.table.column 格式：当输入 db.table. 时提示列
              const threePartMatch = linePrefix.match(QUERY_EDITOR_SQL_THREE_PART_COMPLETION_REGEX);
              if (threePartMatch) {
                  const dbPart = stripQuotes(threePartMatch[1]);
                  const tablePart = stripQuotes(threePartMatch[2]);
                  const colPrefix = (threePartMatch[3] || '').toLowerCase();

                  const cols = await getCompletionColumnsByTable(dbPart, tablePart);
                  if (isSqlCompletionRequestCancelled(token)) {
                      return createEmptySqlCompletionResult();
                  }

                  const filtered = colPrefix
                      ? cols.filter(c => (c.name || '').toLowerCase().startsWith(colPrefix))
                      : cols;

                  const suggestions = filtered.map(c => ({
                      label: c.name,
                      kind: monaco.languages.CompletionItemKind.Field,
                      insertText: quoteCompletionPart(c.name),
                      detail: buildColumnCompletionDetail(c),
                      documentation: buildColumnCompletionDocumentation(c),
                      range,
                      sortText: '0' + c.name
                  }));
                  return { suggestions };
              }

              // 1) 两段式 qualifier.xxx 格式
              const qualifierMatch = linePrefix.match(QUERY_EDITOR_SQL_QUALIFIER_COMPLETION_REGEX);
              if (qualifierMatch) {
                  const qualifier = stripQuotes(qualifierMatch[1]);
                  const prefix = (qualifierMatch[2] || '').toLowerCase();
                  const qualifierLower = qualifier.toLowerCase();

                  // 首先检查 qualifier 是否是数据库名（跨库表提示）
                  const visibleDbs = sharedVisibleDbs;
                  if (visibleDbs.some(db => db.toLowerCase() === qualifierLower)) {
                      // qualifier 是数据库名，提示该库的表
                      let tables = sharedTablesData.filter(t =>
                          (t.dbName || '').toLowerCase() === qualifierLower
                      );
                      if (tables.length === 0) {
                          tables = await getLazyTablesByDB(qualifier);
                          if (isSqlCompletionRequestCancelled(token)) {
                              return createEmptySqlCompletionResult();
                          }
                      }
                      const filtered = prefix
                          ? tables.filter(t => {
                              const suggestionMeta = buildDbQualifiedTableSuggestionMeta(t.dbName || qualifier, t.tableName || '');
                              return String(suggestionMeta.displayName || '').toLowerCase().startsWith(prefix)
                                  || String(t.tableName || '').toLowerCase().startsWith(prefix);
                          })
                          : tables;

                      const suggestions = filtered.map(t => {
                          const suggestionMeta = buildDbQualifiedTableSuggestionMeta(t.dbName || qualifier, t.tableName || '');
                          return {
                          label: suggestionMeta.displayName,
                          kind: monaco.languages.CompletionItemKind.Class,
                          insertText: suggestionMeta.insertText,
                          detail: appendCommentToDetail(`${translate('query_editor.object_info.table')} (${t.dbName})`, t.comment),
                          documentation: buildCompletionDocumentation(t.comment),
                          range,
                          sortText: '0' + suggestionMeta.displayName
                      };
                      });
                      const routineSuggestions = sharedRoutinesData
                          .filter((routine) => String(routine.dbName || '').toLowerCase() === qualifierLower)
                          .map((routine) => ({ routine, meta: buildRoutineSuggestionMeta(routine) }))
                          .filter(({ routine, meta }) => (
                              !prefix
                              || meta.displayName.toLowerCase().startsWith(prefix)
                              || meta.objectName.toLowerCase().startsWith(prefix)
                              || String(routine.routineName || '').toLowerCase().startsWith(prefix)
                          ))
                          .map(({ routine, meta }) => ({
                              label: meta.displayName,
                              kind: monaco.languages.CompletionItemKind.Function,
                              insertText: meta.insertText,
                              insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                              detail: `${getRoutineTypeLabel(routine.routineType)} (${routine.dbName})`,
                              range,
                              sortText: '1' + meta.displayName,
                          }));
                      return { suggestions: [...suggestions, ...routineSuggestions] };
                  }

                  // qualifier 是 schema（如 dbo/public）时，仅补全表名，避免输入 dbo. 后再补成 dbo.dbo.table
                  const schemaTables = sharedTablesData
                      .map(t => {
                          const parsed = splitSchemaAndTable(t.tableName || '');
                          return {
                              dbName: t.dbName || '',
                              schema: parsed.schema,
                              table: parsed.table,
                              comment: t.comment,
                          };
                      })
                      .filter(t => t.schema.toLowerCase() === qualifierLower && !!t.table);

                  if (schemaTables.length > 0) {
                      const filtered = prefix
                          ? schemaTables.filter(t => t.table.toLowerCase().startsWith(prefix))
                          : schemaTables;

                      const suggestions = filtered.map(t => ({
                          label: t.table,
                          kind: monaco.languages.CompletionItemKind.Class,
                          insertText: quoteCompletionPart(t.table),
                          detail: appendCommentToDetail(`${translate('query_editor.object_info.table')} (${t.dbName}${t.schema ? '.' + t.schema : ''})`, t.comment),
                          documentation: buildCompletionDocumentation(t.comment),
                          range,
                          sortText: '0' + t.table
                      }));
                      const routineSuggestions = sharedRoutinesData
                          .filter((routine) => {
                              const meta = buildRoutineSuggestionMeta(routine);
                              return meta.schemaName.toLowerCase() === qualifierLower;
                          })
                          .map((routine) => ({ routine, meta: buildRoutineSuggestionMeta(routine) }))
                          .filter(({ meta }) => !prefix || meta.objectName.toLowerCase().startsWith(prefix))
                          .map(({ routine, meta }) => ({
                              label: meta.objectName,
                              kind: monaco.languages.CompletionItemKind.Function,
                              insertText: `${quoteCompletionPart(meta.objectName)}($0)`,
                              insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                              detail: `${getRoutineTypeLabel(routine.routineType)} (${routine.dbName}${meta.schemaName ? '.' + meta.schemaName : ''})`,
                              range,
                              sortText: '1' + meta.objectName,
                          }));
                      return { suggestions: [...suggestions, ...routineSuggestions] };
                  }

                  // 否则检查是否是表别名或表名，提示列
                  const aliasMap = buildQueryEditorAliasMap(completionReferenceText, sharedCurrentDb || '');

                  const tableInfo = aliasMap[qualifier.toLowerCase()];
                  if (tableInfo) {
                      const cols = await getCompletionColumnsByTable(tableInfo.dbName, tableInfo.tableName);
                      if (isSqlCompletionRequestCancelled(token)) {
                          return createEmptySqlCompletionResult();
                      }

                      const filtered = prefix
                          ? cols.filter(c => (c.name || '').toLowerCase().startsWith(prefix))
                          : cols;

                      const suggestions = filtered.map(c => ({
                          label: c.name,
                          kind: monaco.languages.CompletionItemKind.Field,
                          insertText: quoteCompletionPart(c.name),
                          detail: buildColumnCompletionDetail(c),
                          documentation: buildColumnCompletionDocumentation(c),
                          range,
                          sortText: '0' + c.name
                      }));
                      return { suggestions };
                  }
              }

              // 2) global/table/column completion
              const tableRegex = QUERY_EDITOR_SQL_TABLE_REFERENCE_REGEX;
              tableRegex.lastIndex = 0;
              const foundTables = new Set<string>();
              let match;
              while ((match = tableRegex.exec(completionReferenceText)) !== null) {
                  const t = normalizeQualifiedName(match[1] || '');
                  if (!t) continue;
                  // 存储完整标识 db.table 或 table
                  foundTables.add(t.toLowerCase());
              }

              const currentDatabase = getActiveCompletionDbName();
              const isCurrentCompletionDatabase = (dbName: string) =>
                  String(dbName || '').toLowerCase() === currentDatabase.toLowerCase();
              const wordPrefix = (word.word || '').toLowerCase();
              const startsWithPrefix = (candidate: string) => !wordPrefix || candidate.toLowerCase().startsWith(wordPrefix);
              const includesWordPrefix = (candidate: string) => !wordPrefix || String(candidate || '').toLowerCase().includes(wordPrefix);
              const getPrefixMatchRank = (...candidates: string[]) => {
                  if (!wordPrefix) return '0';
                  const normalized = candidates
                      .map((candidate) => String(candidate || '').toLowerCase())
                      .filter(Boolean);
                  if (normalized.some((candidate) => candidate.startsWith(wordPrefix))) return '0';
                  if (normalized.some((candidate) => candidate.includes(wordPrefix))) return '1';
                  return '9';
              };
              const expectsTableName = /\b(?:FROM|JOIN|UPDATE|INTO|DELETE\s+FROM|TABLE|DESCRIBE|DESC|EXPLAIN)\s+[`"]?[\w.]*$/i.test(linePrefix);
              const expectsRoutineName = /\bCALL\s+[`"]?[\w.]*$/i.test(linePrefix);
              const matchesKeywordPrefix = wordPrefix.length > 0
                  && dialectKeywords.some((keyword) => keyword.toLowerCase().startsWith(wordPrefix));
              const statementPrefixBeforeWord = currentStatementPrefix.slice(
                  0,
                  Math.max(0, currentStatementPrefix.length - String(word.word || '').length),
              );
              const isNewStatementKeywordContext = !expectsTableName
                  && !expectsRoutineName
                  && matchesKeywordPrefix
                  && !statementPrefixBeforeWord.trim();
              const shouldBoostKeywords = !expectsTableName
                  && !expectsRoutineName
                  && matchesKeywordPrefix;
              const sortGroups = isNewStatementKeywordContext
                  ? { keyword: '00', func: '10', routineCurrent: '11', routineOther: '12', tableCurrent: '20', tableOther: '21', columnCurrent: '30', columnOther: '31', db: '40' }
                  : shouldBoostKeywords
                  ? { keyword: '00', func: '05', routineCurrent: '06', routineOther: '07', columnCurrent: '10', columnOther: '11', tableCurrent: '20', tableOther: '21', db: '30' }
                  : expectsRoutineName
                      ? { keyword: '30', func: '40', routineCurrent: '00', routineOther: '01', columnCurrent: '20', columnOther: '21', tableCurrent: '10', tableOther: '11', db: '35' }
                  : expectsTableName
                      ? { keyword: '20', func: '25', routineCurrent: '26', routineOther: '27', columnCurrent: '10', columnOther: '11', tableCurrent: '00', tableOther: '01', db: '30' }
                      : { keyword: '30', func: '25', routineCurrent: '26', routineOther: '27', columnCurrent: '00', columnOther: '01', tableCurrent: '10', tableOther: '11', db: '20' };
              let completionTables = sharedTablesData;
              if (
                  expectsTableName
                  && currentDatabase
                  && !sharedTablesData.some((t) => isCurrentCompletionDatabase(t.dbName || ''))
              ) {
                  const lazyTables = await getLazyTablesByDB(currentDatabase);
                  if (isSqlCompletionRequestCancelled(token)) {
                      return createEmptySqlCompletionResult();
                  }
                  if (lazyTables.length > 0) {
                      const seenTableKeys = new Set<string>();
                      completionTables = [...sharedTablesData, ...lazyTables].filter((table) => {
                          const key = `${String(table.dbName || '').toLowerCase()}.${String(table.tableName || '').toLowerCase()}`;
                          if (seenTableKeys.has(key)) return false;
                          seenTableKeys.add(key);
                          return true;
                      });
                  }
              }
              if (
                  expectsTableName
                  && currentDatabase
                  && sharedTablesData.some((table) => isCurrentCompletionDatabase(table.dbName || ''))
                  && sharedTablesData.some((table) => isCurrentCompletionDatabase(table.dbName || '') && !normalizeCommentText(table.comment))
              ) {
                  const enrichedTables = await getLazyTablesByDB(currentDatabase);
                  if (isSqlCompletionRequestCancelled(token)) {
                      return createEmptySqlCompletionResult();
                  }
                  if (enrichedTables.length > 0) {
                      completionTables = sharedTablesData;
                  }
              }
              if (expectsTableName && currentDatabase) {
                  completionTables = completionTables.filter((table) => isCurrentCompletionDatabase(table.dbName || ''));
              }

              const referencedColumns: CompletionColumnMeta[] = [];
              if (!expectsTableName) {
                  const aliasMapForReferencedTables = buildQueryEditorAliasMap(completionReferenceText, currentDatabase);
                  const seenReferencedTables = new Set<string>();
                  for (const tableInfo of Object.values(aliasMapForReferencedTables)) {
                      const key = `${String(tableInfo.dbName || '').toLowerCase()}.${String(tableInfo.tableName || '').toLowerCase()}`;
                      if (!tableInfo.dbName || !tableInfo.tableName || seenReferencedTables.has(key)) continue;
                      seenReferencedTables.add(key);
                      const preloaded = findPreloadedColumns(tableInfo.dbName, tableInfo.tableName);
                      if (preloaded.length > 0) continue;
                      const cols = await getCompletionColumnsByTable(tableInfo.dbName, tableInfo.tableName);
                      if (isSqlCompletionRequestCancelled(token)) {
                          return createEmptySqlCompletionResult();
                      }
                      referencedColumns.push(...cols);
                  }
              }
              // 相关列提示：匹配 SQL 中引用的表（FROM/JOIN 等）
              // 权重最高，输入 WHERE 条件时优先显示
              // 先用索引把候选收敛到被引用表的列，避免整库列全量扫描。
              const relevantColumnCandidates = expectsTableName || foundTables.size === 0
                  ? []
                  : [
                      ...collectSharedColumnsForTableIdents(sharedAllColumnsData, foundTables),
                      ...referencedColumns,
                  ];
              const relevantColumns = relevantColumnCandidates
                  .filter(c => {
                      const fullIdent = `${c.dbName}.${c.tableName}`.toLowerCase();
                      const shortIdent = (c.tableName || '').toLowerCase();
                      // 对 schema.table 格式，也用纯表名部分匹配（如 public.users → users）
                      const parsed = splitSchemaAndTable(c.tableName || '');
                      const pureIdent = (parsed.table || '').toLowerCase();
                      return (foundTables.has(fullIdent) || foundTables.has(shortIdent) || (pureIdent && foundTables.has(pureIdent))) && startsWithPrefix(c.name || '');
                  })
                  .map(c => {
                      // 当前库的表字段优先级更高
                      const isCurrentDb = isCurrentCompletionDatabase(c.dbName || '');
                      return {
                          label: c.name,
                          kind: monaco.languages.CompletionItemKind.Field,
                          insertText: quoteCompletionPart(c.name),
                          detail: buildColumnCompletionDetail(c),
                          documentation: buildColumnCompletionDocumentation(c),
                          range,
                          sortText: isCurrentDb ? sortGroups.columnCurrent + c.name : sortGroups.columnOther + c.name,
                      };
                  });

              // 表提示：当前库智能处理 schema.table 格式
              // 1. 构建纯表名到 schema 列表的映射，检测同名表
              const currentDbTables = completionTables.filter(t =>
                  isCurrentCompletionDatabase(t.dbName || '')
              );
              const tableNameToSchemas = new Map<string, string[]>();
              for (const t of currentDbTables) {
                  const parsed = splitSchemaAndTable(t.tableName || '');
                  const pureTable = (parsed.table || t.tableName || '').toLowerCase();
                  const schemas = tableNameToSchemas.get(pureTable) || [];
                  schemas.push(parsed.schema || '');
                  tableNameToSchemas.set(pureTable, schemas);
              }

              const tableSuggestions = completionTables
                .filter(t => {
                    const isCurrentDb = isCurrentCompletionDatabase(t.dbName || '');
                    const parsed = splitSchemaAndTable(t.tableName || '');
                    const pureTable = parsed.table || t.tableName || '';
                  if (!isCurrentDb) {
                      const suggestionMeta = buildDbQualifiedTableSuggestionMeta(t.dbName || '', t.tableName || '');
                      const label = suggestionMeta.dbQualifiedLabel;
                      // 跨库：用 db.table 格式匹配
                        return includesWordPrefix(label)
                            || includesWordPrefix(t.tableName || '')
                            || includesWordPrefix(pureTable);
                    }
                    // 当前库：同时用完整名和纯表名匹配
                    return includesWordPrefix(t.tableName || '') || includesWordPrefix(pureTable);
                })
                .map(t => {
                  const isCurrentDb = isCurrentCompletionDatabase(t.dbName || '');
                  const parsed = splitSchemaAndTable(t.tableName || '');
                  const pureTable = parsed.table || t.tableName || '';
                  if (!isCurrentDb) {
                      const suggestionMeta = buildDbQualifiedTableSuggestionMeta(t.dbName || '', t.tableName || '');
                      const label = suggestionMeta.dbQualifiedLabel;
                      return {
                          label,
                          kind: monaco.languages.CompletionItemKind.Class,
                          insertText: quoteCompletionPath(label),
                          detail: appendCommentToDetail(`${translate('query_editor.object_info.table')} (${t.dbName})`, t.comment),
                          documentation: buildCompletionDocumentation(t.comment),
                          range,
                          sortText: sortGroups.tableOther + getPrefixMatchRank(label, t.tableName || '', pureTable) + label,
                      };
                  }
                  // 当前库：检查是否有跨 schema 同名表
                  const schemas = tableNameToSchemas.get(pureTable.toLowerCase()) || [];
                  const hasDuplicate = schemas.length > 1;
                  // 同名表存在于多个 schema → 显示 schema.table；否则只显示纯表名
                  const label = hasDuplicate ? t.tableName : pureTable;
                  const insertText = quoteCompletionPath(hasDuplicate ? t.tableName : pureTable);
                  const schemaInfo = parsed.schema ? ` (${parsed.schema})` : '';
                  return {
                      label,
                      kind: monaco.languages.CompletionItemKind.Class,
                      insertText,
                      detail: appendCommentToDetail(`${translate('query_editor.object_info.table')}${schemaInfo}`, t.comment),
                      documentation: buildCompletionDocumentation(t.comment),
                      range,
                      sortText: sortGroups.tableCurrent + getPrefixMatchRank(t.tableName || '', pureTable) + pureTable,
                  };
              });

              const routineSuggestions = sharedRoutinesData
                  .map((routine) => ({ routine, meta: buildRoutineSuggestionMeta(routine) }))
                  .filter(({ routine, meta }) => {
                      if (expectsRoutineName && meta.routineType !== 'PROCEDURE') return false;
                      return includesWordPrefix(meta.dbQualifiedLabel)
                          || includesWordPrefix(meta.displayName)
                          || includesWordPrefix(meta.objectName)
                          || includesWordPrefix(routine.routineName || '');
                  })
                  .map(({ routine, meta }) => {
                      const isCurrentDb = isCurrentCompletionDatabase(routine.dbName || '');
                      const schemaInfo = meta.schemaName && meta.schemaName.toLowerCase() !== String(routine.dbName || '').toLowerCase()
                          ? `.${meta.schemaName}`
                          : '';
                      return {
                          label: meta.dbQualifiedLabel,
                          kind: monaco.languages.CompletionItemKind.Function,
                          insertText: meta.insertText,
                          insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                          detail: `${getRoutineTypeLabel(routine.routineType)} (${routine.dbName}${schemaInfo})`,
                          range,
                          sortText: (isCurrentDb ? sortGroups.routineCurrent : sortGroups.routineOther)
                              + getPrefixMatchRank(meta.dbQualifiedLabel, meta.displayName, meta.objectName, routine.routineName || '')
                              + meta.dbQualifiedLabel,
                      };
                  });

              // 数据库提示
              const dbSuggestions = sharedVisibleDbs
                  .filter((db) => startsWithPrefix(db))
                  .map(db => ({
                      label: db,
                      kind: monaco.languages.CompletionItemKind.Module,
                      insertText: db,
                      detail: translate('query_editor.object_info.database'),
                      range,
                      sortText: sortGroups.db + db,
                  }));

              // 关键字提示
              const keywordSuggestions = dialectKeywords
                  .filter((k) => startsWithPrefix(k))
                  .map(k => ({
                  label: k,
                  kind: monaco.languages.CompletionItemKind.Keyword,
                  insertText: k,
                  range,
                  sortText: sortGroups.keyword + k,
              }));

              // 内置函数提示
              const funcSuggestions = dialectFunctions
                  .filter((f) => startsWithPrefix(f.name))
                  .map(f => ({
                      label: f.name,
                      kind: monaco.languages.CompletionItemKind.Function,
                      insertText: f.name + '($0)',
                      insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                      detail: f.detail,
                      range,
                      sortText: sortGroups.func + f.name,
                  }));

              const suggestions = isNewStatementKeywordContext
                  ? [
                      ...keywordSuggestions,
                      ...funcSuggestions,
                      ...tableSuggestions,
                      ...dbSuggestions,
                      ...routineSuggestions,
                      ...relevantColumns,
                  ]
                  : [
                      ...relevantColumns,   // FROM 表的列最优先
                      ...tableSuggestions,  // 表次之
                      ...dbSuggestions,     // 数据库
                      ...routineSuggestions, // 存储过程/函数
                      ...funcSuggestions,   // 内置函数
                      ...keywordSuggestions // 关键字最后
                  ];
              return { suggestions };
          }
      }));
      sqlCompletionDisposables.push(monaco.languages.registerCompletionItemProvider('sql', {
          triggerCharacters: ['/'],
          provideCompletionItems: (model: any, position: any) => {
              const lineContent = model.getLineContent(position.lineNumber);
              const textBefore = lineContent.substring(0, position.column - 1).trimStart();
              if (!textBefore.startsWith('/')) {
                  return { suggestions: [] };
              }

              const range = {
                  startLineNumber: position.lineNumber,
                  endLineNumber: position.lineNumber,
                  startColumn: position.column - textBefore.length,
                  endColumn: position.column,
              };

              return {
                  suggestions: ((window as any).__gonaviSlashCmdDefs || []).map((c: any, i: number) => ({
                      label: `${c.cmd}  ${c.label}`,
                      kind: monaco.languages.CompletionItemKind.Event,
                      detail: c.desc,
                      insertText: `__AI_${c.cmd.slice(1).toUpperCase()}__`,
                      range,
                      sortText: String(i).padStart(2, '0'),
                  })),
              };
          },
      }));


      // SQL snippet completion provider
      sqlCompletionDisposables.push(monaco.languages.registerCompletionItemProvider('sql', {
          provideCompletionItems: (model: any, position: any) => {
              const word = model.getWordUntilPosition(position);
              const prefix = word.word.toLowerCase();
              if (!prefix) return createEmptySqlCompletionResult();

              const range = {
                  startLineNumber: position.lineNumber,
                  endLineNumber: position.lineNumber,
                  startColumn: word.startColumn,
                  endColumn: word.endColumn,
              };

              const allSnippets = useStore.getState().sqlSnippets || [];
              const matched = allSnippets.filter(s =>
                  s.prefix.toLowerCase().startsWith(prefix) ||
                  s.name.toLowerCase().includes(prefix)
              );

              return {
                  suggestions: matched.map(s => ({
                      label: s.prefix,
                      kind: monaco.languages.CompletionItemKind.Snippet,
                      insertText: s.body,
                      insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
                      detail: s.name,
                      documentation: s.syntaxHelp || s.description || s.body,
                      range,
                      sortText: '04' + s.prefix,
                  })),
              };
          },
      }));

      } // end sqlCompletionRegistered guard

      // 每个编辑器实例都注册内容变化监听（检测斜杠命令标记）
      let _handlingSlash = false;
      editor.onDidChangeModelContent((event: any) => {
          if (_handlingSlash) return;
          const hasSlashCommandMarker = Array.isArray(event?.changes)
              && event.changes.some((change: any) => /__AI_\w+__/.test(String(change?.text || '')));
          if (!hasSlashCommandMarker) return;
          const model = editor.getModel();
          if (!model) return;
          const content = model.getValue();
          const markerMatch = content.match(/__AI_(\w+)__/);
          if (!markerMatch) return;

          const cmdKey = markerMatch[1].toLowerCase();
          const defs = (window as any).__gonaviSlashCmdDefs || [];
          const cmdDef = defs.find((c: any) => c.cmd === `/${cmdKey}`);
          if (!cmdDef) return;

          // 清除标记文本（带递归保护）
          _handlingSlash = true;
          const fullText = model.getValue();
          const newText = fullText.replace(markerMatch[0], '').replace(/^\s*\n/, '');
          model.setValue(newText);
          _handlingSlash = false;

          // 组装 prompt
          const conn = connectionsRef.current.find(c => c.id === currentConnectionIdRef.current);
          const ctxText = buildQueryEditorAiContextPrompt(conn, currentDbRef.current);
          let finalPrompt = ctxText + cmdDef.prompt;
          if (cmdDef.useSelection) {
              const sel = editor.getSelection();
              const selText = sel ? model.getValueInRange(sel) : '';
              finalPrompt = finalPrompt.replace(QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER, selText || getCurrentQuery());
          }

          // 打开 AI 面板并注入 prompt
          const store = useStore.getState();
          if (!store.aiPanelVisible) {
              store.setAIPanelVisible(true);
          }
          setTimeout(() => {
              window.dispatchEvent(new CustomEvent('gonavi:ai:inject-prompt', { detail: { prompt: finalPrompt } }));
          }, store.aiPanelVisible ? 0 : 350);
      });
  };

  const handleFormat = () => {
      try {
          const activeConnectionId = String(currentConnectionIdRef.current || '').trim();
          const tabConnectionId = String(tab.connectionId || '').trim();
          const conn = connectionsRef.current.find(c => c.id === activeConnectionId)
              || (tabConnectionId && tabConnectionId !== activeConnectionId
                  ? connectionsRef.current.find(c => c.id === tabConnectionId)
                  : undefined);
          const formatterLanguage = resolveQueryEditorFormatterLanguage(conn);
          const sourceSql = getCurrentQuery();
          const formatted = format(sourceSql, { language: formatterLanguage, keywordCase: sqlFormatOptions.keywordCase });
          if (sourceSql === formatted) {
              return;
          }
          updateQueryTabDraft(tab.id, {
              formatRestoreSnapshot: {
                  query: sourceSql,
                  createdAt: Date.now(),
              },
          });
          const editor = editorRef.current;
          const monaco = monacoRef.current;
          const model = editor?.getModel?.();
          if (editor && monaco && model) {
              const currentValue = String(model.getValue?.() || sourceSql);
              if (currentValue === formatted) {
                  return;
              }
              const fullRange = model.getFullModelRange?.()
                  || new monaco.Range(1, 1, model.getLineCount?.() || 1, model.getLineMaxColumn?.(model.getLineCount?.() || 1) || 1);
              editor.pushUndoStop?.();
              editor.executeEdits?.('gonavi-format-sql', [{
                  range: fullRange,
                  text: formatted,
                  forceMoveMarkers: true,
              }]);
              editor.pushUndoStop?.();
              const nextValue = editor.getValue?.();
              applyQueryState(typeof nextValue === 'string' ? nextValue : formatted);
              refreshObjectDecorations();
              return;
      }
      syncQueryToEditor(formatted);
  } catch (e) {
          void message.error(translate('query_editor.message.format_failed'));
      }
  };

  const handleFormatRef = useRef(handleFormat);
  useEffect(() => {
      handleFormatRef.current = handleFormat;
  });

  useEffect(() => {
      const handleFormatActiveQuery = () => {
          if (!isActive) {
              return;
          }
          handleFormatRef.current();
      };

      window.addEventListener('gonavi:format-active-query', handleFormatActiveQuery as EventListener);
      return () => {
          window.removeEventListener('gonavi:format-active-query', handleFormatActiveQuery as EventListener);
      };
  }, [isActive]);

  const handleRestoreLastFormat = () => {
      const previousQuery = tab.formatRestoreSnapshot?.query;
      if (!previousQuery) {
          void message.info(translate('query_editor.message.no_format_restore_snapshot'));
          return;
      }
      syncQueryToEditor(previousQuery);
      updateQueryTabDraft(tab.id, {
          query: previousQuery,
          formatRestoreSnapshot: undefined,
      });
      refreshObjectDecorations();
      void message.success(translate('query_editor.message.format_restore_success'));
  };

  const handleAIAction = (action: 'generate' | 'explain' | 'optimize' | 'schema') => {
      if (action === 'generate') {
          openTextToSqlModal();
          return;
      }

      const editor = editorRef.current;
      const selection = editor?.getModel()?.getValueInRange(editor.getSelection()) || '';
      const fullSQL = getCurrentQuery();

      const conn = connections.find(c => c.id === currentConnectionId);
      const ctxText = buildQueryEditorAiContextPrompt(conn, currentDb);

      const prompts: Record<string, string> = {
          generate: `${ctxText}${translate('query_editor.ai_prompt.generate')}`,
          explain: `${ctxText}${translate('query_editor.ai_prompt.explain', { sql: selection || fullSQL || QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER })}`,
          optimize: `${ctxText}${translate('query_editor.ai_prompt.optimize', { sql: selection || fullSQL || QUERY_EDITOR_SQL_PROMPT_PLACEHOLDER })}`,
          schema: `${ctxText}${translate('query_editor.ai_prompt.schema')}`,
      };

      const store = useStore.getState();
      if (!store.aiPanelVisible) {
          store.setAIPanelVisible(true);
      }
      window.dispatchEvent(new CustomEvent('gonavi:ai:inject-prompt', { detail: { prompt: prompts[action] } }));
  };

  const formatSettingsMenu: MenuProps['items'] = [
      { 
          key: 'upper', 
          label: translate('query_editor.format.keyword_upper'),
          icon: sqlFormatOptions.keywordCase === 'upper' ? '✓' : undefined,
          onClick: () => setSqlFormatOptions({ keywordCase: 'upper' }) 
      },
      { 
          key: 'lower', 
          label: translate('query_editor.format.keyword_lower'),
          icon: sqlFormatOptions.keywordCase === 'lower' ? '✓' : undefined,
          onClick: () => setSqlFormatOptions({ keywordCase: 'lower' }) 
      },
      { type: 'divider' },
      {
          key: 'restore-last-format',
          label: translate('query_editor.format.restore_last_format'),
          disabled: !tab.formatRestoreSnapshot?.query,
          onClick: handleRestoreLastFormat,
      },
      { type: 'divider' },
      {
          key: 'snippet-settings',
          label: translate('query_editor.format.snippet_settings'),
          onClick: () => window.dispatchEvent(new CustomEvent('gonavi:open-snippet-settings')),
      },
      {
          key: 'shortcut-settings',
          label: translate('query_editor.format.shortcut_settings'),
          onClick: () => window.dispatchEvent(new CustomEvent('gonavi:open-shortcut-settings')),
      },
  ];

  const splitSQLStatements = (sql: string): string[] => {
    return findSqlStatementRanges(sql).map((range) => range.text);
  };

  const containsOraclePlsqlDefinition = (statements: string[]): boolean => (
      statements.some((statement) => /^\s*(?:(?:--[^\n]*|\/\*[\s\S]*?\*\/)\s*)*CREATE\s+(?:OR\s+REPLACE\s+)?(?:EDITIONABLE\s+|NONEDITIONABLE\s+)?(?:PROCEDURE|FUNCTION|PACKAGE|TRIGGER)\b/i.test(statement))
  );

  const normalizeOracleSqlPlusSlashTerminators = (sql: string): string => (
      String(sql || '').replace(/(^|\n)([ \t]*\/[ \t]*);+([ \t]*(?:--[^\n]*)?)(?=\n|$)/g, '$1$2$3')
  );

  const getSelectedSQL = (): string => {
      const editor = editorRef.current;
      if (!editor) return '';
      const model = editor.getModel?.();
      const selection = editor.getSelection?.();
      if (!model || !selection) return '';

      const selected = model.getValueInRange?.(selection) || '';
      if (typeof selected !== 'string') return '';
      if (!selected.trim()) return '';
      return selected;
  };

  const buildResultSetMergeKey = (result: ResultSet): string => {
      const sqlKey = normalizeExecutedSqlKey(result.exportSql || result.sql);
      const sourceStatementIndex = Number(result.sourceStatementIndex || 1);
      const statementResultIndex = Number(result.statementResultIndex || 1);
      return `${sqlKey}::${sourceStatementIndex}::${statementResultIndex}`;
  };

  const mergeResultSets = (previous: ResultSet[], next: ResultSet[], replaceAll: boolean): ResultSet[] => {
      if (replaceAll || previous.length === 0) {
          return next.map((result, index) => ({ ...result, key: `result-${index + 1}` }));
      }

      const merged = [...previous];
      next.forEach((result) => {
          const incomingKey = buildResultSetMergeKey(result);
          const existingIndex = merged.findIndex((item) => buildResultSetMergeKey(item) === incomingKey);
          if (existingIndex >= 0) {
              merged[existingIndex] = { ...result, key: merged[existingIndex].key };
              return;
          }
          merged.push({ ...result, key: `result-${resolveNextResultSetIndex(merged)}` });
      });
      return merged;
  };

  const isDisplayableResultSet = (result?: ResultSet | null): boolean => {
      if (!result) {
          return false;
      }
      if (Array.isArray(result.messages) && result.messages.length > 0) {
          return true;
      }
      if (Array.isArray(result.columns) && result.columns.length > 0) {
          return true;
      }
      if (Array.isArray(result.rows) && result.rows.length > 0) {
          return true;
      }
      return false;
  };

  const isAffectedRowsResultSet = (result?: ResultSet | null): boolean =>
      Boolean(
          result &&
          Array.isArray(result.columns) &&
          result.columns.length === 1 &&
          result.columns[0] === 'affectedRows',
      );

  const isAffectedRowsResultSetData = (result?: any): boolean =>
      Boolean(
          result &&
          Array.isArray(result.rows) &&
          result.rows.length === 1 &&
          Array.isArray(result.columns) &&
          result.columns.length === 1 &&
          result.columns[0] === 'affectedRows',
      );

  const hasConcreteQueryResultSetData = (result: any, messages: string[]): boolean => {
      if (!result || isAffectedRowsResultSetData(result)) return false;
      if (messages.length > 0) return true;
      if (Array.isArray(result.columns) && result.columns.length > 0) return true;
      if (Array.isArray(result.rows) && result.rows.length > 0) return true;
      return false;
  };

  const isMessageLikeResultSet = (result?: ResultSet | null): boolean =>
      Boolean(
          result &&
          Array.isArray(result.messages) &&
          result.messages.length > 0 &&
          result.resultType !== 'grid',
      );

  const isConcreteGridResultSet = (result?: ResultSet | null): boolean =>
      Boolean(
          result &&
          result.resultType !== 'message' &&
          !isAffectedRowsResultSet(result) &&
          (
              (Array.isArray(result.columns) && result.columns.length > 0) ||
              (Array.isArray(result.rows) && result.rows.length > 0)
          ),
      );

  const resolveActiveResultKeyAfterMerge = (merged: ResultSet[], executed: ResultSet[]): string => {
      const firstExecutedResult = executed.find((result) => isConcreteGridResultSet(result))
          || executed.find((result) => isMessageLikeResultSet(result))
          || executed.find((result) => isDisplayableResultSet(result) && !isAffectedRowsResultSet(result))
          || executed.find((result) => isDisplayableResultSet(result))
          || executed[0];
      if (!firstExecutedResult) {
          return '';
      }
      const executedSqlKey = buildResultSetMergeKey(firstExecutedResult);
      return merged.find((item) => buildResultSetMergeKey(item) === executedSqlKey)?.key
          || firstExecutedResult.key
          || merged[0]?.key
          || '';
  };

  const resolveExecutableSQLAtEditorPosition = (model: any, sqlText: string, position: any): string => {
      const normalizedPosition = normalizeEditorPosition(position);
      if (!normalizedPosition) return '';
      const cursorOffset = getNormalizedOffsetAtPosition(sqlText, normalizedPosition);
      const resolved = resolveExecutableSql(sqlText, cursorOffset, '');
      return resolved?.sql || '';
  };

  const getExecutableSQLAtCurrentCursor = (model: any, sqlText: string): string => {
      const editor = editorRef.current;
      const liveSelection = normalizeEditorPosition(editor?.getSelection?.());
      if (liveSelection) {
          return resolveExecutableSQLAtEditorPosition(model, sqlText, liveSelection);
      }

      const livePosition = normalizeEditorPosition(editor?.getPosition?.());
      const cachedPosition = normalizeEditorPosition(lastEditorCursorPositionRef.current);
      const candidates: Array<{ lineNumber: number; column: number }> = [];
      if (cachedPosition) candidates.push(cachedPosition);
      if (livePosition) candidates.push(livePosition);
      const seen = new Set<string>();

      for (const position of candidates) {
          const key = `${position.lineNumber}:${position.column}`;
          if (seen.has(key)) continue;
          seen.add(key);
          const sql = resolveExecutableSQLAtEditorPosition(model, sqlText, position);
          if (sql.trim()) return sql;
      }

      const fallbackPosition = cachedPosition || livePosition;
      return resolveExecutableSQLAtEditorPosition(model, sqlText, fallbackPosition);
  };

  const getExecutableSQL = (): string => {
      const editor = editorRef.current;
      const model = editor?.getModel?.();
      const currentQuery = getCurrentQuery();
      const selectedSQL = getSelectedSQL();
      const selected = selectedSQL.trim();
      if (!selected && resultSets.length > 0 && lastExecutedEditorQueryRef.current && currentQuery.startsWith(lastExecutedEditorQueryRef.current)) {
          const appendedSQL = currentQuery.slice(lastExecutedEditorQueryRef.current.length);
          if (appendedSQL.trim()) {
              return appendedSQL;
          }
      }
      if (!model || !editor) {
          return selectedSQL || currentQuery;
      }

      if (selected) {
          return selectedSQL;
      }
      return getExecutableSQLAtCurrentCursor(model, String(model.getValue?.() ?? currentQuery));
  };

  const captureEditorCursorPosition = (event?: React.MouseEvent<HTMLElement>) => {
      event?.preventDefault();
      const editor = editorRef.current;
      const position = normalizeEditorPosition(editor?.getSelection?.()) || normalizeEditorPosition(editor?.getPosition?.());
      if (position) {
          lastEditorCursorPositionRef.current = position;
      }
  };

  const executeSqlEditorMultiQuery = useCallback((
      config: Record<string, any>,
      dbName: string,
      sql: string,
      queryId: string,
      sourceStatements: string[],
  ) => {
      const pendingTransaction = pendingSqlTransactionRef.current;
      if (pendingTransaction && canReusePendingSqlEditorTransactionForType(String(config.type || ''), sourceStatements)) {
          return DBQueryMultiInTransaction(pendingTransaction.id, sql, queryId);
      }
      return DBQueryMulti(buildRpcConnectionConfig(config) as any, dbName, sql, queryId);
  }, []);

  // 精准重查询单个结果集（提交事务 / 刷新按钮使用），不会重跑整个编辑器 SQL
  const handleReloadResult = async (resultKey: string, sql: string) => {
      if (!sql?.trim() || !currentDb) return;
      const conn = connections.find(c => c.id === currentConnectionId);
      if (!conn) return;
      const currentResult = resultSets.find((item) => item.key === resultKey);
      const statementResultIndex = Math.max(1, Number(currentResult?.statementResultIndex || 1));

      const config = {
          ...conn.config,
          port: Number(conn.config.port),
          password: conn.config.password || "",
          database: conn.config.database || "",
          useSSH: conn.config.useSSH || false,
          ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
      };

      try {
          setLoading(true);
          // 保持与首次执行一致的后端路径，必要时复用挂起事务
          let queryId: string;
          try {
              queryId = await GenerateQueryID();
          } catch {
              queryId = 'reload-' + Date.now();
          }
          const res = await executeSqlEditorMultiQuery(
              config,
              currentDb,
              sql,
              queryId,
              splitSQLStatements(sql),
          );
          if (!res?.success) {
              message.error(translate('query_editor.message.refresh_failed', {
                  error: formatSqlExecutionError(res?.message || translate('common.unknown'), { translate }),
              }));
              return;
          }

          const resultSetDataArray = Array.isArray(res.data) ? (res.data as any[]) : [];
          const rsData = resultSetDataArray[Math.max(0, statementResultIndex - 1)];
          if (!rsData) return;
          const isAffectedResult = Array.isArray(rsData.rows) && rsData.rows.length === 1
              && rsData.columns && rsData.columns.length === 1
              && rsData.columns[0] === 'affectedRows';
          if (isAffectedResult) return; // 不应该出现，但保险起见

          let rows = Array.isArray(rsData.rows) ? rsData.rows : [];
          const maxRows = Number(queryOptions?.maxRows) || 0;
          let truncated = false;
          if (Number.isFinite(maxRows) && maxRows > 0 && rows.length > maxRows) {
              truncated = true;
              rows = rows.slice(0, maxRows);
          }
          const cols = (rsData.columns && rsData.columns.length > 0)
              ? rsData.columns
              : (rows.length > 0 ? Object.keys(rows[0]) : []);
          const refreshedMessages = normalizeQueryResultMessages(rsData?.messages);
          rows.forEach((row: any, i: number) => {
              if (row && typeof row === 'object') row[GONAVI_ROW_KEY] = i;
          });

          // 只更新匹配的结果集的 rows 和 columns，保留 tableName/pkColumns/readOnly 等元数据
          setResultSets(prev => prev.map(rs =>
              rs.key === resultKey
                  ? {
                      ...rs,
                      rows,
                      columns: cols,
                      messages: refreshedMessages,
                      resultType: ((!Array.isArray(rsData.rows) || rsData.rows.length === 0) && (!Array.isArray(rsData.columns) || rsData.columns.length === 0) && refreshedMessages.length > 0)
                          ? 'message'
                          : 'grid',
                      truncated,
                  }
                  : rs
          ));
      } catch (err: any) {
          message.error(translate('query_editor.message.refresh_failed', {
              error: formatSqlExecutionError(err?.message || err || translate('common.unknown'), { translate }),
          }));
      } finally {
          setLoading(false);
      }
  };

  const handleResultPageChange = async (resultKey: string, page: number, pageSize: number) => {
      const target = resultSets.find((item) => item.key === resultKey);
      if (!target?.page?.baseSql || !currentDb) return;
      const conn = connections.find(c => c.id === currentConnectionId);
      if (!conn) return;
      const safePage = Math.max(1, Math.floor(Number(page) || 1));
      const safePageSize = Math.max(1, Math.floor(Number(pageSize) || target.page.pageSize || 1));
      const config = {
          ...conn.config,
          port: Number(conn.config.port),
          password: conn.config.password || "",
          database: conn.config.database || "",
          useSSH: conn.config.useSSH || false,
          ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" }
      };
      const dbType = String(config.type || 'mysql');
      const driver = String((config as any).driver || '');
      const normalizedDbType = String(resolveSqlDialect(dbType, driver, {
          oceanBaseProtocol: String((config as any).oceanBaseProtocol || ''),
      })).toLowerCase();
      const pageSql = buildQueryResultPageSql({
          baseSql: target.page.baseSql,
          dbType: normalizedDbType,
          driver,
          page: safePage,
          pageSize: safePageSize,
          lookahead: true,
      });

      try {
          setLoading(true);
          setResultSets(prev => prev.map(rs =>
              rs.key === resultKey && rs.page
                  ? { ...rs, page: { ...rs.page, loading: true } }
                  : rs
          ));
          let queryId: string;
          try {
              queryId = await GenerateQueryID();
          } catch {
              queryId = 'query-page-' + Date.now();
          }
          const res = await executeSqlEditorMultiQuery(
              config,
              currentDb,
              pageSql,
              queryId,
              splitSQLStatements(pageSql),
          );
          if (!res?.success) {
              message.error(translate('query_editor.message.page_query_failed', {
                  error: formatSqlExecutionError(res?.message || translate('common.unknown'), { translate }),
              }));
              return;
          }

          const resultSetDataArray = Array.isArray(res.data) ? (res.data as any[]) : [];
          const rsData = resultSetDataArray[0];
          if (!rsData) {
              message.warning(translate('query_editor.message.page_query_empty'));
              return;
          }
          const rawRows = Array.isArray(rsData.rows) ? rsData.rows : [];
          const hasNext = rawRows.length > safePageSize;
          const rows = rawRows.slice(0, safePageSize);
          const rowKeyOffset = (safePage - 1) * safePageSize;
          rows.forEach((row: any, i: number) => {
              if (row && typeof row === 'object') row[GONAVI_ROW_KEY] = rowKeyOffset + i;
          });
          const cols = (rsData.columns && rsData.columns.length > 0)
              ? rsData.columns
              : (rows.length > 0 ? Object.keys(rows[0]) : target.columns);
          const pageMessages = normalizeQueryResultMessages(rsData?.messages);
          const totalState = resolveQueryResultPaginationTotal({
              current: safePage,
              pageSize: safePageSize,
              rowCount: rows.length,
              hasNext,
          });
          setResultSets(prev => prev.map(rs =>
              rs.key === resultKey && rs.page
                  ? {
                      ...rs,
                      rows,
                      columns: cols,
                      messages: pageMessages,
                      resultType: 'grid',
                      truncated: false,
                      page: {
                          ...rs.page,
                          current: safePage,
                          pageSize: safePageSize,
                          ...totalState,
                          loading: false,
                      },
                  }
                  : rs
          ));
      } catch (err: any) {
          message.error(translate('query_editor.message.page_query_failed', {
              error: formatSqlExecutionError(err?.message || err || translate('common.unknown'), { translate }),
          }));
      } finally {
          setLoading(false);
          setResultSets(prev => prev.map(rs =>
              rs.key === resultKey && rs.page?.loading
                  ? { ...rs, page: { ...rs.page, loading: false } }
                  : rs
          ));
      }
  };

  const handleRun = async () => {
    const currentQuery = getCurrentQuery();
    if (!currentQuery.trim()) return;
    const executableSQL = getExecutableSQL();
    if (!executableSQL.trim()) {
        message.info(translate('query_editor.message.no_executable_sql'));
        setResultSets([]);
        setActiveResultKey('');
        return;
    }
    if (!currentDb) {
        message.error(translate('query_editor.message.select_database_first'));
        return;
    }
    // 如果已有查询在运行，先取消它
    if (currentQueryIdRef.current) {
        try {
            await CancelQuery(currentQueryIdRef.current);
        } catch (error) {
            // 忽略取消错误，可能查询已完成
        }
        // 清除旧查询ID
        clearQueryId();
    }
      const runSeq = ++runSeqRef.current;
      setLoading(true);
      setExecutionError('');
      const runStartTime = Date.now();
    const conn = connections.find(c => c.id === currentConnectionId);
    if (!conn) {
        message.error(translate('query_editor.message.connection_not_found'));
        if (runSeqRef.current === runSeq) setLoading(false);
        return;
    }
    const connCaps = getDataSourceCapabilities(conn.config);
	    if (!connCaps.supportsQueryEditor) {
	        message.error(translate('query_editor.message.unsupported_source'));
	        if (runSeqRef.current === runSeq) setLoading(false);
	        return;
	    }
	    if (findConnectionMutatingStatements(conn.config, executableSQL).length > 0) {
	        message.warning(translate('query_editor.message.connection_readonly_blocked'));
	        if (runSeqRef.current === runSeq) setLoading(false);
	        return;
	    }

	    const config = {
        ...conn.config,
        port: Number(conn.config.port),
        password: conn.config.password || "",
        database: conn.config.database || "",
        useSSH: conn.config.useSSH || false,
        ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" },
        timeout: Math.max(Number(conn.config.timeout) || 30, 120),
    };

    try {
        const rawSQL = executableSQL;
        const rpcConfig = buildRpcConnectionConfig(config) as any;
        const dbType = String(rpcConfig.type || 'mysql');
        const driver = String((config as any).driver || '');
        const normalizedDbType = String(resolveSqlDialect(dbType, driver, {
            oceanBaseProtocol: (config as any).oceanBaseProtocol,
        })).trim().toLowerCase();
        const normalizedRawSQL = String(rawSQL || '').replace(/；/g, ';');

        // MongoDB 仍走逐条执行的旧路径
        const isMongoDB = normalizedDbType === 'mongodb';

        if (isMongoDB) {
            // MongoDB: 保持逐条执行
            const splitInput = normalizedRawSQL
                .replace(/^\s*\/\/.*$/gm, '')
                .replace(/^\s*#.*$/gm, '');
            const statements = splitSQLStatements(splitInput);
            const didExecuteAppendedSql = resultSets.length > 0
                && lastExecutedEditorQueryRef.current
                && currentQuery.startsWith(lastExecutedEditorQueryRef.current)
                && normalizedRawSQL.trim() === currentQuery.slice(lastExecutedEditorQueryRef.current.length).replace(/；/g, ';').trim();
            const didExecuteWholeEditor = areSqlStatementListsEqual(
                splitSQLStatements(currentQuery.replace(/；/g, ';')),
                statements,
            );
            if (statements.length === 0) {
                message.info(translate('query_editor.message.no_executable_sql'));
                setResultSets([]);
                setActiveResultKey('');
                return;
            }

            const nextResultSets: ResultSet[] = [];
            const maxRows = Number(queryOptions?.maxRows) || 0;
            const wantsLimitProbe = Number.isFinite(maxRows) && maxRows > 0;
            let anyTruncated = false;

            for (let idx = 0; idx < statements.length; idx++) {
                const rawStatement = statements[idx];
                let executedSql = rawStatement;
                const shellConvert = convertMongoShellToJsonCommand(executedSql);
                if (shellConvert.recognized) {
                    if (shellConvert.error) {
                        const prefix = statements.length > 1
                            ? translate('query_editor.message.statement_failed_prefix', { index: idx + 1 })
                            : '';
                        updateResultPanelVisibility(true);
                        setExecutionError(formatSqlExecutionError(shellConvert.error, { prefix, translate }));
                        setResultSets([]);
                        setActiveResultKey('');
                        return;
                    }
                    if (shellConvert.command) {
                        executedSql = shellConvert.command;
                    }
                }
                if (wantsLimitProbe) {
                    const limitResult = applyMongoQueryAutoLimit(executedSql, maxRows);
                    if (limitResult.applied) {
                        executedSql = limitResult.command;
                    }
                }
                const startTime = Date.now();
                let queryId: string;
                try {
                    queryId = await GenerateQueryID();
                } catch (error) {
                    console.warn('GenerateQueryID failed, using local UUID fallback:', error);
                    queryId = 'query-' + uuidv4();
                }
                setQueryId(queryId);

                const res = await DBQueryWithCancel(buildRpcConnectionConfig(config) as any, currentDb, executedSql, queryId);
                const legacyResultMessages = normalizeQueryResultMessages(res?.messages);
                const duration = Date.now() - startTime;
                addSqlLog({
                    id: `log-${Date.now()}-query-${idx + 1}`,
                    timestamp: Date.now(),
                    sql: executedSql,
                    status: res.success ? 'success' : 'error',
                    duration,
                    message: res.success ? '' : res.message,
                    affectedRows: (res.success && !Array.isArray(res.data)) ? (res.data as any).affectedRows : (Array.isArray(res.data) ? res.data.length : undefined),
                    dbName: currentDb
                });
                if (!res.success) {
                    const prefix = statements.length > 1
                        ? translate('query_editor.message.statement_failed_prefix', { index: idx + 1 })
                        : '';
                    updateResultPanelVisibility(true);
                    setExecutionError(formatSqlExecutionError(res.message, { prefix, translate }));
                    setResultSets([]);
                    setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY);
                    return;
                }
                if (Array.isArray(res.data)) {
                    let rows = (res.data as any[]) || [];
                    let truncated = false;
                    if (wantsLimitProbe && Number.isFinite(maxRows) && maxRows > 0 && rows.length > maxRows) {
                        truncated = true;
                        anyTruncated = true;
                        rows = rows.slice(0, maxRows);
                    }
                    const cols = (res.fields && res.fields.length > 0)
                        ? (res.fields as string[])
                        : (rows.length > 0 ? Object.keys(rows[0]) : []);
                    rows.forEach((row: any, i: number) => {
                        if (row && typeof row === 'object') row[GONAVI_ROW_KEY] = i;
                    });
                    nextResultSets.push({
                        key: `result-${idx + 1}`,
                        sql: rawStatement,
                        exportSql: rawStatement,
                        sourceStatementIndex: idx + 1,
                        statementResultIndex: 1,
                        rows,
                        columns: cols,
                        messages: legacyResultMessages,
                        pkColumns: [],
                        readOnly: true,
                        truncated
                    });
                } else if (legacyResultMessages.length > 0) {
                    nextResultSets.push({
                        key: `result-${idx + 1}`,
                        sql: rawStatement,
                        exportSql: rawStatement,
                        sourceStatementIndex: idx + 1,
                        statementResultIndex: 1,
                        rows: [],
                        columns: [],
                        messages: legacyResultMessages,
                        resultType: 'message',
                        pkColumns: [],
                        readOnly: true,
                    });
                } else {
                    const affected = Number((res.data as any)?.affectedRows);
                    if (Number.isFinite(affected)) {
                        const row = { affectedRows: affected };
                        (row as any)[GONAVI_ROW_KEY] = 0;
                        nextResultSets.push({
                            key: `result-${idx + 1}`,
                            sql: rawStatement,
                            exportSql: rawStatement,
                            sourceStatementIndex: idx + 1,
                            statementResultIndex: 1,
                            rows: [row],
                            columns: ['affectedRows'],
                            messages: legacyResultMessages,
                            pkColumns: [],
                            readOnly: true
                        });
                    }
                }
            }
            if (nextResultSets.length > 0) {
                updateResultPanelVisibility(true);
            }
            const shouldReplaceAllResults = didExecuteWholeEditor;
            const mergedResultSets = mergeResultSets(resultSets, nextResultSets, shouldReplaceAllResults);
            setResultSets(mergedResultSets);
            setActiveResultKey(resolveActiveResultKeyAfterMerge(mergedResultSets, nextResultSets));
            if (didExecuteAppendedSql || didExecuteWholeEditor) {
                lastExecutedEditorQueryRef.current = currentQuery;
            }
            if (statements.length > 1) {
                message.success(translate('query_editor.message.execution_multi_success', {
                    statements: statements.length,
                    results: nextResultSets.length,
                }));
            } else if (nextResultSets.length === 0) {
                message.success(translate('query_editor.message.execution_success'));
            }

        } else {
            // 非 MongoDB：使用 DBQueryMulti 一次性执行多条 SQL，后端返回多结果集
            const sourceStatements = splitSQLStatements(normalizedRawSQL);
            const didExecuteAppendedSql = resultSets.length > 0
                && lastExecutedEditorQueryRef.current
                && currentQuery.startsWith(lastExecutedEditorQueryRef.current)
                && normalizedRawSQL.trim() === currentQuery.slice(lastExecutedEditorQueryRef.current.length).replace(/；/g, ';').trim();
            const didExecuteWholeEditor = areSqlStatementListsEqual(
                splitSQLStatements(currentQuery.replace(/；/g, ';')),
                sourceStatements,
            );
            if (sourceStatements.length === 0) {
                message.info(translate('query_editor.message.no_executable_sql'));
                setResultSets([]);
                setActiveResultKey('');
                return;
            }
            const useManagedTransaction = shouldUseSqlEditorManagedTransactionForType(connCaps.type, sourceStatements);
            if (useManagedTransaction && pendingSqlTransactionRef.current) {
                message.warning(translate('query_editor.transaction.message.pending_managed_transaction'));
                return;
            }
            const managedTransactionStatementCount = sourceStatements
                .filter((statement) => shouldUseSqlEditorManagedTransactionForType(connCaps.type, [statement]))
                .length || sourceStatements.length;

            const forceReadOnlyResult = connCaps.forceReadOnlyQueryResult;
            const defaultOracleSchema = isOracleLikeDialect(normalizedDbType)
                ? resolveOracleLikeDefaultSchemaName(config)
                : '';
            const oracleTableCache = new Map<string, CompletionTableMeta[]>();
            const getOracleTablesForDb = async (dbName: string): Promise<CompletionTableMeta[]> => {
                const normalizedDbName = String(dbName || '').trim();
                if (!normalizedDbName) return [];
                const cacheKey = normalizedDbName.toLowerCase();
                const cached = oracleTableCache.get(cacheKey);
                if (cached) return cached;

                const existing = tablesRef.current.filter((table) => String(table.dbName || '').trim().toLowerCase() === cacheKey);
                if (existing.length > 0) {
                    oracleTableCache.set(cacheKey, existing);
                    return existing;
                }

                try {
                    const resTables = await DBGetTables(buildRpcConnectionConfig(config) as any, normalizedDbName);
                    if (!resTables?.success || !Array.isArray(resTables.data)) {
                        oracleTableCache.set(cacheKey, []);
                        return [];
                    }
                    const fetchedTables = resTables.data
                        .map((row: any) => {
                            const tableName = String(Object.values(row || {})[0] || '').trim();
                            if (!tableName) return null;
                            return {
                                dbName: normalizedDbName,
                                tableName,
                            } as CompletionTableMeta;
                        })
                        .filter(Boolean) as CompletionTableMeta[];
                    if (fetchedTables.length > 0) {
                        const knownKeys = new Set(tablesRef.current.map((table) => `${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim()}`));
                        const missing = fetchedTables.filter((table) => !knownKeys.has(`${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim()}`));
                        if (missing.length > 0) {
                            tablesRef.current = [...tablesRef.current, ...missing];
                            if (isActive) {
                                sharedTablesData = tablesRef.current;
                            }
                        }
                    }
                    oracleTableCache.set(cacheKey, fetchedTables);
                    return fetchedTables;
                } catch {
                    oracleTableCache.set(cacheKey, []);
                    return [];
                }
            };
            const executedSourceStatements: string[] = [];
            for (const statement of sourceStatements) {
                let executableStatement = statement;
                if (isOracleLikeDialect(normalizedDbType)) {
                    const leadingTable = matchLeadingSelectTableReference(statement);
                    if (leadingTable) {
                        const leadingSegments = splitQueryIdentifierPathSegments(leadingTable.tableText);
                        const oracleLookupDbCandidates = leadingSegments.length >= 2
                            ? [String(leadingSegments[0]?.value || '').trim()].filter(Boolean)
                            : resolveOracleLikeLookupSchemaCandidates(config, currentDb);
                        let exactQualifiedTable: string | undefined;
                        for (const oracleLookupDbName of oracleLookupDbCandidates) {
                            const oracleTables = oracleLookupDbName ? await getOracleTablesForDb(oracleLookupDbName) : [];
                            exactQualifiedTable = resolveOracleExactCaseTableReference(statement, oracleLookupDbName, oracleTables, {
                                qualifyUnqualified: Boolean(
                                    leadingSegments.length === 1
                                    && oracleLookupDbName
                                    && oracleLookupDbName.toLowerCase() !== String(defaultOracleSchema || '').trim().toLowerCase(),
                                ),
                            });
                            if (exactQualifiedTable) {
                                break;
                            }
                        }
                        if (exactQualifiedTable) {
                            executableStatement = rewriteLeadingSelectTableReference(statement, exactQualifiedTable) || statement;
                        }
                    }
                }
                executedSourceStatements.push(executableStatement);
            }
            const statementPlans: QueryStatementPlan[] = [];
            for (let index = 0; index < sourceStatements.length; index += 1) {
                const statementForPlan = executedSourceStatements[index] || sourceStatements[index];
                try {
                    statementPlans.push(await resolveQueryLocatorPlan({
                        statement: statementForPlan,
                        originalStatement: sourceStatements[index],
                        dbType: normalizedDbType,
                        currentDb,
                        config,
                        forceReadOnly: forceReadOnlyResult,
                    }));
                } catch (planError) {
                    // 行定位计划失败绝不能阻断查询执行，兜底裸计划保证结果页始终呈现。
                    console.warn('resolveQueryLocatorPlan failed; falling back to a bare statement plan', planError);
                    statementPlans.push({
                        originalSql: sourceStatements[index],
                        executedSql: statementForPlan,
                        pkColumns: [],
                    });
                }
            }

            // 自动给 SELECT 语句注入行数限制（防止大结果集卡死）
            const maxRowsForLimit = Number(queryOptions?.maxRows) || 0;
            let anyLimitApplied = false;
            const executablePlans = statementPlans.map((plan) => {
                if (!Number.isFinite(maxRowsForLimit) || maxRowsForLimit <= 0) return plan;
                const result = applyQueryAutoLimit(plan.executedSql, normalizedDbType, maxRowsForLimit, driver);
                if (result.applied) anyLimitApplied = true;
                return { ...plan, executedSql: result.sql };
            });
            const executableStatements = executablePlans.map((plan) => plan.executedSql);
            const shouldPreserveOraclePlsqlBatch = isOracleLikeDialect(normalizedDbType) && containsOraclePlsqlDefinition(sourceStatements);
            const fullSQL = shouldPreserveOraclePlsqlBatch
                ? normalizeOracleSqlPlusSlashTerminators(normalizedRawSQL)
                : executableStatements.join(';\n');

            const startTime = Date.now();
            let queryId: string;
            try {
                queryId = await GenerateQueryID();
            } catch (error) {
                console.warn('GenerateQueryID failed, using local UUID fallback:', error);
                queryId = 'query-' + uuidv4();
            }
            setQueryId(queryId);

            const res = useManagedTransaction
                ? await DBQueryMultiTransactional(buildRpcConnectionConfig(config) as any, currentDb, fullSQL, queryId)
                : await executeSqlEditorMultiQuery(
                    config,
                    currentDb,
                    fullSQL,
                    queryId,
                    executableStatements,
                );
            const duration = Date.now() - startTime;

            addSqlLog({
                id: `log-${Date.now()}-query-multi`,
                timestamp: Date.now(),
                sql: sourceStatements.join(';\n'),
                status: res.success ? 'success' : 'error',
                duration,
                message: res.success ? '' : res.message,
                dbName: currentDb
            });

            if (!res.success) {
                const errorMsg = res.message.toLowerCase();
                const isCancelledError = errorMsg.includes('context canceled') ||
                                         errorMsg.includes('查询已取消') ||
                                         errorMsg.includes('canceled') ||
                                         errorMsg.includes('cancelled') ||
                                         errorMsg.includes('statement canceled') ||
                                         errorMsg.includes('sql: statement canceled');
                const isTimeoutError = errorMsg.includes('context deadline exceeded') ||
                                       errorMsg.includes('timeout') ||
                                       hasLocalizedSqlTimeoutKeyword(errorMsg) ||
                                       errorMsg.includes('deadline exceeded');

                if (isCancelledError && !isTimeoutError) {
                    setResultSets([]);
                    setActiveResultKey('');
                    if (currentQueryIdRef.current) {
                        clearQueryId();
                    }
                    return;
                }

                updateResultPanelVisibility(true);
                setExecutionError(formatSqlExecutionError(res.message, { translate }));
                setResultSets([]);
                setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY);
                return;
            }

            if (useManagedTransaction && res.transactionPending && res.transactionId) {
                activatePendingSqlTransaction({
                    id: String(res.transactionId),
                    commitMode: sqlEditorCommitMode,
                    autoCommitDelayMs: sqlEditorAutoCommitDelayMs,
                    createdAt: Date.now(),
                    statementCount: managedTransactionStatementCount,
                });
            }

            // res.data 是 ResultSetData[] 数组
            const resultSetDataArray = Array.isArray(res.data) ? (res.data as any[]) : [];
            const topLevelMessages = normalizeQueryResultMessages(res.messages);
            const nextResultSets: ResultSet[] = [];
            const maxRows = Number(queryOptions?.maxRows) || 0;
            let anyTruncated = false;
            const statementResultCounts = new Map<number, number>();
            const resolveSourceStatementIndex = (rsData: any, idx: number): number => {
                const explicitStatementIndex = Number(rsData?.statementIndex || 0);
                if (explicitStatementIndex > 0) {
                    return explicitStatementIndex;
                }
                if (normalizedDbType === 'sqlserver' && sourceStatements.length === 1) {
                    return 1;
                }
                return idx + 1;
            };
            const sqlServerStatementsWithConcreteResults = new Set<number>();
            if (normalizedDbType === 'sqlserver') {
                resultSetDataArray.forEach((rsData, idx) => {
                    const sourceStatementIndex = resolveSourceStatementIndex(rsData, idx);
                    const resultMessages = normalizeQueryResultMessages(rsData?.messages);
                    if (hasConcreteQueryResultSetData(rsData, resultMessages)) {
                        sqlServerStatementsWithConcreteResults.add(sourceStatementIndex);
                    }
                });
            }
            const shouldUseTopLevelSqlServerMessages = normalizedDbType === 'sqlserver'
                && topLevelMessages.length > 0
                && sqlServerStatementsWithConcreteResults.size === 0;

            for (let idx = 0; idx < resultSetDataArray.length; idx++) {
                const rsData = resultSetDataArray[idx];
                const sourceStatementIndex = resolveSourceStatementIndex(rsData, idx);
                const plan = executablePlans[Math.max(0, sourceStatementIndex - 1)];
                const originalSql = plan?.originalSql || '';
                const executedSql = plan?.executedSql || originalSql;
                const resultMessages = normalizeQueryResultMessages(rsData?.messages);

                // 检查是否为 affectedRows 类结果集
                const isAffectedResult = isAffectedRowsResultSetData(rsData);
                const shouldHideSqlServerAffectedResult = normalizedDbType === 'sqlserver'
                    && isAffectedResult
                    && (
                        sqlServerStatementsWithConcreteResults.has(sourceStatementIndex)
                        || shouldUseTopLevelSqlServerMessages
                    );
                if (shouldHideSqlServerAffectedResult) {
                    continue;
                }

                const statementResultIndex = (statementResultCounts.get(sourceStatementIndex) || 0) + 1;
                statementResultCounts.set(sourceStatementIndex, statementResultIndex);

                if (isAffectedResult) {
                    const affected = Number(rsData.rows[0]?.affectedRows);
                    const row = { affectedRows: Number.isFinite(affected) ? affected : 0 };
                    (row as any)[GONAVI_ROW_KEY] = 0;
                    nextResultSets.push({
                        key: `result-${nextResultSets.length + 1}`,
                        sql: executedSql,
                        exportSql: originalSql,
                        sourceStatementIndex,
                        statementResultIndex,
                        rows: [row],
                        columns: ['affectedRows'],
                        messages: resultMessages,
                        pkColumns: [],
                        readOnly: true
                    });
                } else if ((!Array.isArray(rsData.rows) || rsData.rows.length === 0) && (!Array.isArray(rsData.columns) || rsData.columns.length === 0) && resultMessages.length > 0) {
                    nextResultSets.push({
                        key: `result-${nextResultSets.length + 1}`,
                        sql: executedSql,
                        exportSql: originalSql,
                        sourceStatementIndex,
                        statementResultIndex,
                        rows: [],
                        columns: [],
                        messages: resultMessages,
                        resultType: 'message',
                        pkColumns: [],
                        readOnly: true,
                    });
                } else {
                    let rows = Array.isArray(rsData.rows) ? rsData.rows : [];
                    let truncated = false;
                    // 仅当前端自动注入了 LIMIT 时才做兜底截断；用户手写 LIMIT 时尊重原始结果
                    if (anyLimitApplied && Number.isFinite(maxRows) && maxRows > 0 && rows.length > maxRows) {
                        truncated = true;
                        anyTruncated = true;
                        rows = rows.slice(0, maxRows);
                    }
                    const cols = (rsData.columns && rsData.columns.length > 0)
                        ? rsData.columns
                        : (rows.length > 0 ? Object.keys(rows[0]) : []);

                    rows.forEach((row: any, i: number) => {
                        if (row && typeof row === 'object') row[GONAVI_ROW_KEY] = i;
                    });

                    const tableRef = plan?.tableRef;
                    const editLocator = plan?.editLocator;
                    const page = createInitialQueryResultPagination({
                        executedSql,
                        exportSql: originalSql,
                        dbType: normalizedDbType,
                        driver,
                        returnedRowCount: rows.length,
                        fallbackPageSize: maxRows,
                    });
                    nextResultSets.push({
                        key: `result-${nextResultSets.length + 1}`,
                        sql: executedSql,
                        exportSql: originalSql,
                        sourceStatementIndex,
                        statementResultIndex,
                        rows,
                        columns: cols,
                        messages: resultMessages,
                        tableName: tableRef?.tableName,
                        // 跨库/跨 schema 查询时，列类型与注释必须从真实表所在库加载
                        metadataDbName: tableRef?.metadataDbName,
                        metadataTableName: tableRef?.metadataTableName,
                        pkColumns: plan?.pkColumns || [],
                        editLocator,
                        readOnly: forceReadOnlyResult || !editLocator || editLocator.readOnly,
                        truncated,
                        page,
                    });
                }
            }

            if (topLevelMessages.length > 0 && !nextResultSets.some((result) => Array.isArray(result.messages) && result.messages.length > 0)) {
                nextResultSets.push({
                    key: `result-${nextResultSets.length + 1}`,
                    sql: fullSQL,
                    exportSql: sourceStatements.join(';\n'),
                    sourceStatementIndex: 1,
                    statementResultIndex: (statementResultCounts.get(1) || 0) + 1,
                    rows: [],
                    columns: [],
                    messages: topLevelMessages,
                    resultType: 'message',
                    pkColumns: [],
                    readOnly: true,
                });
            }

            if (nextResultSets.length > 0) {
                updateResultPanelVisibility(true);
            }
            const shouldReplaceAllResults = didExecuteWholeEditor;
            const mergedResultSets = mergeResultSets(resultSets, nextResultSets, shouldReplaceAllResults);
            setResultSets(mergedResultSets);
            setActiveResultKey(resolveActiveResultKeyAfterMerge(mergedResultSets, nextResultSets));
            if (didExecuteAppendedSql || didExecuteWholeEditor) {
                lastExecutedEditorQueryRef.current = currentQuery;
            }

            executablePlans.forEach((plan) => {
                if (plan.warning) message.warning(plan.warning);
            });

            // 后端附带的提示信息（如本次改走逐条执行的多语句回退提示）
            if (res.message) {
                message.info(res.message);
            }
            if (resultSetDataArray.length > 1) {
                message.success(translate('query_editor.message.execution_result_sets_success', {
                    results: nextResultSets.length,
                }));
            } else if (nextResultSets.length === 0) {
                message.success(translate('query_editor.message.execution_success'));
            }

        }
    } catch (e: any) {
        const formattedError = formatSqlExecutionError(e?.message || e, { translate });
        message.error(translate('query_editor.message.execution_failed_with_error', { error: formattedError }));
        addSqlLog({
            id: `log-${Date.now()}-error`,
            timestamp: Date.now(),
            sql: executableSQL || getExecutableSQL() || getCurrentQuery(),
            status: 'error',
            duration: Date.now() - runStartTime,
            message: e.message,
            dbName: currentDb
        });
        updateResultPanelVisibility(true);
        setExecutionError(formattedError);
        setResultSets([]);
        setActiveResultKey(QUERY_EDITOR_SQL_LOG_TAB_KEY);
    } finally {
        if (runSeqRef.current === runSeq) setLoading(false);
        // Clear query ID after execution completes
        clearQueryId();
    }
  };

  const handleRunSelectedShortcut = async () => {
      await handleRun();
  };

  const handleCancel = async () => {
    if (!currentQueryIdRef.current) {
      message.warning(translate('query_editor.message.cancel_no_running'));
      return;
    }
    const queryIdToCancel = currentQueryIdRef.current;
    try {
      const res = await CancelQuery(queryIdToCancel);
      if (res.success) {
        message.success(translate('query_editor.message.cancel_success'));
        // Clear query ID after successful cancellation
        if (currentQueryIdRef.current === queryIdToCancel) {
          clearQueryId()
        }
      } else {
        message.warning(res.message);
      }
    } catch (error: any) {
      message.error(translate('query_editor.message.cancel_failed', { error: error.message }));
    }
  };

  useEffect(() => {
      const handleSelectAllInEditor = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey || event.key.toLowerCase() !== 'a') {
              return;
          }

          const editor = editorRef.current;
          if (!editor) {
              return;
          }

          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor.hasTextFocus?.();
          const inEditorPane = !!(targetNode && editorPaneRef.current?.contains(targetNode));
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (isEditableElement(event.target) && !inEditorPane) {
              return;
          }
          if (!editorHasFocus && !inEditorPane) {
              return;
          }
          if (!editorHasFocus && !inQueryEditor) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          editor.focus?.();
          editor.trigger('keyboard', 'editor.action.selectAll', null);
      };

      window.addEventListener('keydown', handleSelectAllInEditor, true);
      return () => {
          window.removeEventListener('keydown', handleSelectAllInEditor, true);
      };
  }, [isActive]);

  useEffect(() => {
      const binding = runQueryShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleRunShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }
          const editorHasFocus = !!editorRef.current?.hasTextFocus?.();
          if (!editorHasFocus && !isEditableElement(event.target)) {
              return;
          }
          event.preventDefault();
          event.stopPropagation();
          void handleRunSelectedShortcut();
      };

      window.addEventListener('keydown', handleRunShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleRunShortcut, true);
      };
  }, [isActive, runQueryShortcutBinding, handleRun]);

  // Re-register Monaco internal keybinding when runQuery shortcut changes
  useEffect(() => {
      if (objectHoverActionRef.current) {
          objectHoverActionRef.current.dispose();
          objectHoverActionRef.current = null;
      }

      if (!editorRef.current || !monacoRef.current) return;

      registerShowObjectInfoAction();

      return () => {
          if (objectHoverActionRef.current) {
              objectHoverActionRef.current.dispose();
              objectHoverActionRef.current = null;
          }
      };
  }, [languagePreference, registerShowObjectInfoAction]);

  useEffect(() => {
      const editor = editorRef.current;
      if (!editor) return;

      registerInsertSqlSnippetContextMenuAction(editor);

      return () => {
          if (insertSqlSnippetActionRef.current) {
              insertSqlSnippetActionRef.current.dispose();
              insertSqlSnippetActionRef.current = null;
          }
      };
  }, [languagePreference, registerInsertSqlSnippetContextMenuAction]);

  useEffect(() => {
      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      registerTriggerSqlAiCompletionAction(editor, monaco);

      return () => {
          if (triggerSqlAiCompletionActionRef.current) {
              triggerSqlAiCompletionActionRef.current.dispose();
              triggerSqlAiCompletionActionRef.current = null;
          }
      };
  }, [languagePreference, registerTriggerSqlAiCompletionAction]);

  useEffect(() => {
      triggerSqlAiCompletionKeydownDisposableRef.current?.dispose?.();
      triggerSqlAiCompletionKeydownDisposableRef.current = null;

      const editor = editorRef.current;
      const binding = triggerSqlAiCompletionShortcutBinding;
      if (!editor?.onKeyDown || !binding?.enabled || !binding.combo) {
          return;
      }

      triggerSqlAiCompletionKeydownDisposableRef.current = editor.onKeyDown((event: any) => {
          if (!isActive) {
              return;
          }

          const browserEvent = event?.browserEvent || event?.event || event;
          if (!browserEvent) {
              return;
          }
          if (!isTriggerSqlAiCompletionShortcutEvent(browserEvent)) {
              if (isPossibleTriggerSqlAiCompletionFallbackEvent(browserEvent)) {
                  triggerSqlAiCompletionFallbackRef.current = { observedAt: Date.now() };
              }
              return;
          }

          triggerSqlAiCompletionFallbackRef.current = null;
          event?.preventDefault?.();
          event?.stopPropagation?.();
          browserEvent.preventDefault?.();
          browserEvent.stopPropagation?.();
          triggerAiInlineCompletionRef.current?.();
      });

      return () => {
          triggerSqlAiCompletionKeydownDisposableRef.current?.dispose?.();
          triggerSqlAiCompletionKeydownDisposableRef.current = null;
      };
  }, [isActive, isPossibleTriggerSqlAiCompletionFallbackEvent, isTriggerSqlAiCompletionShortcutEvent, triggerSqlAiCompletionShortcutBinding]);

  useEffect(() => {
      if (runQueryActionRef.current) {
          runQueryActionRef.current.dispose();
          runQueryActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = runQueryShortcutBinding;
      if (!binding?.enabled || !binding.combo) return;

      const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
      if (keyBinding) {
          runQueryActionRef.current = editor.addAction({
              id: 'gonavi.runQuery',
              label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.runQuery.label'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: () => {
                  window.dispatchEvent(new CustomEvent('gonavi:run-active-query', {
                      detail: { requireSelection: true },
                  }));
              },
          });
      }

      return () => {
          if (runQueryActionRef.current) {
              runQueryActionRef.current.dispose();
              runQueryActionRef.current = null;
          }
      };
  }, [languagePreference, runQueryShortcutBinding]);

  useEffect(() => {
      if (selectCurrentStatementActionRef.current) {
          selectCurrentStatementActionRef.current.dispose();
          selectCurrentStatementActionRef.current = null;
      }
      if (macFindWithSelectionGuardActionRef.current) {
          macFindWithSelectionGuardActionRef.current.dispose();
          macFindWithSelectionGuardActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = selectCurrentStatementShortcutBinding;
      if (binding?.enabled && binding.combo) {
          const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
          if (keyBinding) {
              selectCurrentStatementActionRef.current = editor.addAction({
                  id: 'gonavi.selectCurrentStatement',
                  label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.selectCurrentStatement.label'),
                  keybindings: [keyBinding.keyMod | keyBinding.keyCode],
                  run: handleSelectCurrentStatement,
              });
          }
      }

      const macFindWithSelectionGuardKeyBinding = activeShortcutPlatform === 'mac'
          ? comboToMonacoKeyBinding(
              QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO,
              monaco.KeyMod,
              monaco.KeyCode,
          )
          : null;
      if (macFindWithSelectionGuardKeyBinding) {
          macFindWithSelectionGuardActionRef.current = editor.addAction({
              id: QUERY_EDITOR_MAC_FIND_WITH_SELECTION_GUARD_ACTION_ID,
              label: 'GoNavi: Suppress macOS Cmd+E Find with Selection',
              keybindings: [
                  macFindWithSelectionGuardKeyBinding.keyMod
                  | macFindWithSelectionGuardKeyBinding.keyCode,
              ],
              run: () => {
                  if (
                      binding?.enabled
                      && normalizeShortcutCombo(binding.combo) === QUERY_EDITOR_MAC_FIND_WITH_SELECTION_COMBO
                  ) {
                      void handleSelectCurrentStatement();
                  }
              },
          });
      }

      return () => {
          if (selectCurrentStatementActionRef.current) {
              selectCurrentStatementActionRef.current.dispose();
              selectCurrentStatementActionRef.current = null;
          }
          if (macFindWithSelectionGuardActionRef.current) {
              macFindWithSelectionGuardActionRef.current.dispose();
              macFindWithSelectionGuardActionRef.current = null;
          }
      };
  }, [activeShortcutPlatform, languagePreference, selectCurrentStatementShortcutBinding, handleSelectCurrentStatement]);

  useEffect(() => {
      if (duplicateCurrentLineActionRef.current) {
          duplicateCurrentLineActionRef.current.dispose();
          duplicateCurrentLineActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = duplicateCurrentLineShortcutBinding;
      if (!binding?.enabled || !binding.combo) return;

      const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
      if (keyBinding) {
          duplicateCurrentLineActionRef.current = editor.addAction({
              id: 'gonavi.duplicateCurrentLine',
              label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.duplicateCurrentLine.label'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: handleDuplicateCurrentLine,
          });
      }

      return () => {
          if (duplicateCurrentLineActionRef.current) {
              duplicateCurrentLineActionRef.current.dispose();
              duplicateCurrentLineActionRef.current = null;
          }
      };
  }, [duplicateCurrentLineShortcutBinding, handleDuplicateCurrentLine, languagePreference]);

  useEffect(() => {
      if (saveQueryActionRef.current) {
          saveQueryActionRef.current.dispose();
          saveQueryActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = saveQueryShortcutBinding;
      if (!binding?.enabled || !binding.combo) return;

      const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
      if (keyBinding) {
          saveQueryActionRef.current = editor.addAction({
              id: 'gonavi.saveQuery',
              label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.saveQuery.label'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: () => {
                  window.dispatchEvent(new CustomEvent('gonavi:save-active-query'));
              },
          });
      }

      return () => {
          if (saveQueryActionRef.current) {
              saveQueryActionRef.current.dispose();
              saveQueryActionRef.current = null;
          }
      };
  }, [languagePreference, saveQueryShortcutBinding]);

  useEffect(() => {
      if (findInEditorActionRef.current) {
          findInEditorActionRef.current.dispose();
          findInEditorActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const keyBinding = comboToMonacoKeyBinding(
          findInEditorShortcutCombo,
          monaco.KeyMod,
          monaco.KeyCode,
      );
      if (keyBinding) {
          findInEditorActionRef.current = editor.addAction({
              id: 'gonavi.findInEditor',
              label: buildQueryEditorMonacoActionLabel('query_editor.action.find_in_editor'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: () => {
                  window.dispatchEvent(new CustomEvent('gonavi:find-active-query'));
              },
          });
      }

      return () => {
          if (findInEditorActionRef.current) {
              findInEditorActionRef.current.dispose();
              findInEditorActionRef.current = null;
          }
      };
  }, [findInEditorShortcutCombo, languagePreference]);

  useEffect(() => {
      if (formatSqlActionRef.current) {
          formatSqlActionRef.current.dispose();
          formatSqlActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = formatSqlShortcutBinding;
      if (!binding?.enabled || !binding.combo) return;

      const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
      if (keyBinding) {
          formatSqlActionRef.current = editor.addAction({
              id: 'gonavi.formatSql',
              label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.formatSql.label'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: () => {
                  window.dispatchEvent(new CustomEvent('gonavi:format-active-query'));
              },
          });
      }

      return () => {
          if (formatSqlActionRef.current) {
              formatSqlActionRef.current.dispose();
              formatSqlActionRef.current = null;
          }
      };
  }, [languagePreference, formatSqlShortcutBinding]);

  useEffect(() => {
      const editor = editorRef.current;
      if (!editor) return;

      registerQueryEditorAiContextMenuActions(editor);

      return () => {
          disposeQueryEditorAiContextMenuActions();
      };
  }, [languagePreference, disposeQueryEditorAiContextMenuActions, registerQueryEditorAiContextMenuActions]);

  useEffect(() => {
      refreshQueryEditorSlashCommandDefs();
  }, [languagePreference, refreshQueryEditorSlashCommandDefs]);

  useEffect(() => {
      if (toggleQueryResultsPanelActionRef.current) {
          toggleQueryResultsPanelActionRef.current.dispose();
          toggleQueryResultsPanelActionRef.current = null;
      }

      const editor = editorRef.current;
      const monaco = monacoRef.current;
      if (!editor || !monaco) return;

      const binding = toggleQueryResultsPanelShortcutBinding;
      if (!binding?.enabled || !binding.combo) return;

      const keyBinding = comboToMonacoKeyBinding(binding.combo, monaco.KeyMod, monaco.KeyCode);
      if (keyBinding) {
          toggleQueryResultsPanelActionRef.current = editor.addAction({
              id: 'gonavi.toggleQueryResultsPanel',
              label: buildQueryEditorMonacoActionLabel('app.shortcuts.action.toggleQueryResultsPanel.label'),
              keybindings: [keyBinding.keyMod | keyBinding.keyCode],
              run: toggleResultPanelVisibility,
          });
      }

      return () => {
          if (toggleQueryResultsPanelActionRef.current) {
              toggleQueryResultsPanelActionRef.current.dispose();
              toggleQueryResultsPanelActionRef.current = null;
          }
      };
  }, [languagePreference, toggleQueryResultsPanelShortcutBinding, toggleResultPanelVisibility]);

  useEffect(() => {
      const handleRunActiveQuery = (event: Event) => {
          if (!isActive) {
              return;
          }
          if ((event as CustomEvent<{ requireSelection?: boolean }>).detail?.requireSelection) {
              void handleRunSelectedShortcut();
              return;
          }
          void handleRun();
      };

      window.addEventListener('gonavi:run-active-query', handleRunActiveQuery as EventListener);
      return () => {
          window.removeEventListener('gonavi:run-active-query', handleRunActiveQuery as EventListener);
      };
  }, [isActive, handleRun, handleRunSelectedShortcut]);

  useEffect(() => {
      const handleFindActiveQuery = () => {
          if (!isActive) {
              return;
          }
          handleOpenEditorFind();
      };

      window.addEventListener('gonavi:find-active-query', handleFindActiveQuery as EventListener);
      return () => {
          window.removeEventListener('gonavi:find-active-query', handleFindActiveQuery as EventListener);
      };
  }, [handleOpenEditorFind, isActive]);

  useEffect(() => {
      const binding = selectCurrentStatementShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleSelectCurrentStatementShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          void handleSelectCurrentStatement();
      };

      window.addEventListener('keydown', handleSelectCurrentStatementShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleSelectCurrentStatementShortcut, true);
      };
  }, [handleSelectCurrentStatement, isActive, selectCurrentStatementShortcutBinding]);

  useEffect(() => {
      const binding = selectCurrentStatementShortcutBinding;
      if (
          activeShortcutPlatform !== 'mac'
          || !binding?.enabled
          || normalizeShortcutCombo(binding.combo) !== 'Meta+E'
      ) {
          return;
      }

      try {
          return EventsOn(QUERY_EDITOR_NATIVE_SELECT_CURRENT_LINE_EVENT, () => {
              if (!isActive) {
                  return;
              }
              void handleSelectCurrentStatement();
          });
      } catch {
          return;
      }
  }, [activeShortcutPlatform, handleSelectCurrentStatement, isActive, selectCurrentStatementShortcutBinding]);

  useEffect(() => {
      const binding = duplicateCurrentLineShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleDuplicateCurrentLineShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          handleDuplicateCurrentLine();
      };

      window.addEventListener('keydown', handleDuplicateCurrentLineShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleDuplicateCurrentLineShortcut, true);
      };
  }, [duplicateCurrentLineShortcutBinding, handleDuplicateCurrentLine, isActive]);

  // 监听由 TabManager 分发的专用注入事件
  useEffect(() => {
      const handleInsertSql = (e: any) => {
          if (e.detail?.tabId !== tab.id || !e.detail?.sql) return;
          const { sql: sqlText, connectionId, dbName } = e.detail;

          // 同步更新 ref，防止异步 fetchDbs 竞态覆盖正确的 dbName
          if (connectionId && connectionId !== currentConnectionId) {
              if (dbName) {
                  currentDbRef.current = dbName;
                  setCurrentDb(dbName);
              }
              setCurrentConnectionId(connectionId);
          } else if (dbName && dbName !== currentDb) {
              currentDbRef.current = dbName;
              setCurrentDb(dbName);
          }


          const editor = editorRef.current;
          const monaco = monacoRef.current;
          if (editor && monaco) {
              const model = editor.getModel();
              const existingContent = editor.getValue?.() || '';

              // runImmediately 模式下，如果编辑器内容已是待注入的 SQL（TabManager 创建时已传入），
              // 跳过追加，直接选中全部内容并执行
              if (e.detail.runImmediately && existingContent.trim() === sqlText.trim()) {
                  if (model) {
                      const lineCount = model.getLineCount();
                      const maxCol = model.getLineMaxColumn(lineCount);
                      editor.setSelection(new monaco.Range(1, 1, lineCount, maxCol));
                      editor.focus();
                      setTimeout(() => handleRun(), 500);
                  }
              } else {
              let position = editor.getPosition();
              if (!position && model) {
                  const lineCount = model.getLineCount();
                  const maxCol = model.getLineMaxColumn(lineCount);
                  position = new monaco.Position(lineCount, maxCol);
              }

              if (position) {
                  const mText = (sqlText.endsWith('\n') ? sqlText : sqlText + '\n');
                  const startRange = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column);
                  
                  editor.executeEdits('ai-insert', [{
                      range: startRange,
                      text: (position.column > 1 ? '\n' : '') + mText,
                      forceMoveMarkers: true
                  }]);
                  const nextValue = editor.getValue?.();
                  if (typeof nextValue === 'string') {
                      applyQueryState(nextValue);
                  }
                  
                  // 定位并滚动到可见区域
                  const targetLine = position.lineNumber + (position.column > 1 ? 1 : 0);
                  editor.revealLineInCenterIfOutsideViewport(targetLine);
                  editor.setPosition({ lineNumber: targetLine + mText.split('\n').length - 1, column: 1 });
                  editor.focus();
                  
                  if (!e.detail.runImmediately) {
                      message.success(translate('query_editor.message.insert_success'));
                  }

                  if (e.detail.runImmediately) {
                      const endPosition = editor.getPosition();
                      editor.setSelection(new monaco.Range(
                          targetLine, 1,
                          endPosition.lineNumber, endPosition.column
                      ));
                      // 🔧 延迟 500ms 等待连接/数据库切换的 setState 生效后再执行
                      setTimeout(() => handleRun(), 500);
                  }
              }
              }
          } else {
              applyQueryState(getCurrentQuery() ? `${getCurrentQuery()}\n${sqlText}` : sqlText);
              message.success(translate('query_editor.message.append_success'));
          }
      };
      window.addEventListener('gonavi:insert-sql-to-tab', handleInsertSql as EventListener);
      return () => window.removeEventListener('gonavi:insert-sql-to-tab', handleInsertSql as EventListener);
  }, [tab.id, handleRun]);

  const resolveDefaultQueryName = () => {
      const rawTitle = String(tab.title || '').trim();
      if (isLocalizedUntitledQueryTitle(rawTitle)) {
          return translate('query_editor.save_modal.unnamed');
      }
      return rawTitle;
  };

  const persistQuery = async (payload: { id: string; name: string; createdAt?: number }) => {
      const sql = getCurrentQuery();
      const saved = {
          id: payload.id,
          name: payload.name,
          sql,
          connectionId: currentConnectionId,
          dbName: currentDb || tab.dbName || '',
          createdAt: payload.createdAt ?? Date.now(),
      };
      const persisted = await saveQuery(saved);
      addTab({
          ...tab,
          title: persisted.name,
          query: sql,
          connectionId: currentConnectionId,
          dbName: currentDb || tab.dbName || '',
          savedQueryId: persisted.id,
      });
      clearQueryTabDraft(tab.id);
      return persisted;
  };

  const openSaveQueryModal = (mode: 'save' | 'rename') => {
      setSaveModalMode(mode);
      saveForm.setFieldsValue({ name: currentSavedQuery?.name || resolveDefaultQueryName() });
      setIsSaveModalOpen(true);
  };

  const handleQuickSave = async () => {
      const filePath = String(tab.filePath || '').trim();
      if (filePath) {
          const sql = getCurrentQuery();
          try {
              const res = await WriteSQLFile(filePath, sql);
              if (!res.success) {
                  message.error(translate('query_editor.message.save_sql_file_failed', {
                      error: res.message || translate('common.unknown'),
                  }));
                  return;
              }
              addTab({
                  ...tab,
                  query: sql,
                  connectionId: currentConnectionId,
                  dbName: currentDb || tab.dbName || '',
                  filePath,
                  savedQueryId: undefined,
              });
              clearQueryTabDraft(tab.id);
              message.success(translate('query_editor.message.sql_file_saved'));
          } catch (error) {
              message.error(translate('query_editor.message.save_sql_file_failed', {
                  error: error instanceof Error ? error.message : String(error),
              }));
          }
          return;
      }

      const existed = currentSavedQuery || null;
      const fallbackSavedId = String(tab.savedQueryId || '').trim();
      const saveId = existed?.id || fallbackSavedId || '';
      if (!saveId) {
          openSaveQueryModal('save');
          return;
      }
      const saveName = existed?.name || resolveDefaultQueryName();
      await persistQuery({ id: saveId, name: saveName, createdAt: existed?.createdAt });
      message.success(translate('query_editor.message.saved'));
  };

  const handleRenameQuery = () => {
      const existed = currentSavedQuery || null;
      const fallbackSavedId = String(tab.savedQueryId || '').trim();
      if (!existed && !fallbackSavedId) {
          message.warning(translate('query_editor.message.save_first_before_rename'));
          openSaveQueryModal('save');
          return;
      }
      openSaveQueryModal('rename');
  };

  const handleExportSQLFile = async () => {
      try {
          const res = await ExportSQLFile(currentSavedQuery?.name || resolveDefaultQueryName(), getCurrentQuery());
          if (!res.success) {
              if ((res.message || '') !== '已取消') {
                  message.error(translate('query_editor.message.export_sql_file_failed', {
                      error: res.message || translate('common.unknown'),
                  }));
              }
              return;
          }
          message.success(translate('query_editor.message.export_sql_file_success'));
      } catch (error) {
          const errorDetail = error instanceof Error
              ? error.message || translate('common.unknown')
              : (typeof (error as any)?.message === 'string' && (error as any).message)
                  || (typeof error === 'string' && error)
                  || translate('common.unknown');
          message.error(translate('query_editor.message.export_sql_file_failed', {
              error: errorDetail,
          }));
      }
  };

  const saveMoreMenuItems: MenuProps['items'] = [
      {
          key: 'rename-query',
          label: translate('query_editor.action.rename_query'),
          disabled: !!tab.filePath,
          onClick: handleRenameQuery,
      },
      {
          key: 'export-sql-file',
          label: translate('query_editor.action.export_sql_file'),
          onClick: () => void handleExportSQLFile(),
      },
      { type: 'divider' },
      {
          key: 'diagnose-query',
          label: (
              <span>
                  {translate('app.shortcuts.action.diagnoseQuery.label' as any)}
                  {diagnoseQueryShortcutBinding?.enabled && diagnoseQueryShortcutBinding.combo && (
                      <span style={{ marginLeft: 8, color: 'var(--gn-text-muted, #6c757d)', fontSize: 11 }}>
                          {getShortcutDisplayLabel(diagnoseQueryShortcutBinding.combo, activeShortcutPlatform)}
                      </span>
                  )}
              </span>
          ),
          onClick: () => openSqlAnalysisWorkbench('diagnose', getCurrentQuery()),
      },
      {
          key: 'show-slow-queries',
          label: (
              <span>
                  {translate('app.shortcuts.action.showSlowQueries.label' as any)}
                  {showSlowQueriesShortcutBinding?.enabled && showSlowQueriesShortcutBinding.combo && (
                      <span style={{ marginLeft: 8, color: 'var(--gn-text-muted, #6c757d)', fontSize: 11 }}>
                          {getShortcutDisplayLabel(showSlowQueriesShortcutBinding.combo, activeShortcutPlatform)}
                      </span>
                  )}
              </span>
          ),
          onClick: () => openSqlAnalysisWorkbench('slow-query'),
      },
  ];

  useEffect(() => {
      const handleFindShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, findInEditorShortcutCombo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inEditorPane = !!(targetNode && editorPaneRef.current?.contains(targetNode));
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (isEditableElement(event.target) && !inEditorPane) {
              return;
          }
          if (!editorHasFocus && !inEditorPane && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          handleOpenEditorFind();
      };

      window.addEventListener('keydown', handleFindShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleFindShortcut, true);
      };
  }, [findInEditorShortcutCombo, handleOpenEditorFind, isActive]);

  useEffect(() => {
      const binding = saveQueryShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleSaveShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          void handleQuickSave();
      };

      window.addEventListener('keydown', handleSaveShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleSaveShortcut, true);
      };
  }, [isActive, saveQueryShortcutBinding, handleQuickSave]);

  useEffect(() => {
      const binding = formatSqlShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleFormatShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          handleFormatRef.current();
      };

      window.addEventListener('keydown', handleFormatShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleFormatShortcut, true);
      };
  }, [isActive, formatSqlShortcutBinding]);

  useEffect(() => {
      const updateAltState = (event: KeyboardEvent) => {
          const key = String(event.key || '').trim().toLowerCase();
          const code = String(event.code || '').trim().toLowerCase();
          const isAltKey = key === 'alt'
              || code === 'altleft'
              || code === 'altright';
          if (isAltKey) {
              if (event.type === 'keydown') {
                  triggerSqlAiCompletionAltGestureAtRef.current = Date.now();
              }
              triggerSqlAiCompletionAltPressedRef.current = event.type !== 'keyup';
          } else if (event.type === 'keyup' && !event.altKey) {
              triggerSqlAiCompletionAltPressedRef.current = false;
          }
      };
      const clearAltState = () => {
          triggerSqlAiCompletionAltPressedRef.current = false;
          triggerSqlAiCompletionAltGestureAtRef.current = 0;
          triggerSqlAiCompletionFallbackRef.current = null;
      };

      window.addEventListener('keydown', updateAltState, true);
      window.addEventListener('keyup', updateAltState, true);
      window.addEventListener('blur', clearAltState);
      return () => {
          window.removeEventListener('keydown', updateAltState, true);
          window.removeEventListener('keyup', updateAltState, true);
          window.removeEventListener('blur', clearAltState);
      };
  }, []);

  useEffect(() => {
      const binding = triggerSqlAiCompletionShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleTriggerSqlAiCompletionShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }
          if (!isTriggerSqlAiCompletionShortcutEvent(event)) {
              if (isPossibleTriggerSqlAiCompletionFallbackEvent(event)) {
                  triggerSqlAiCompletionFallbackRef.current = { observedAt: Date.now() };
              }
              return;
          }

          triggerSqlAiCompletionFallbackRef.current = null;
          event.preventDefault();
          event.stopPropagation();
          triggerAiInlineCompletionRef.current?.();
      };

      window.addEventListener('keydown', handleTriggerSqlAiCompletionShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleTriggerSqlAiCompletionShortcut, true);
      };
  }, [isActive, isPossibleTriggerSqlAiCompletionFallbackEvent, isTriggerSqlAiCompletionShortcutEvent, triggerSqlAiCompletionShortcutBinding]);

  useEffect(() => {
      const binding = toggleQueryResultsPanelShortcutBinding;
      if (!binding?.enabled || !binding.combo) {
          return;
      }

      const handleToggleResultsShortcut = (event: KeyboardEvent) => {
          if (!isActive) {
              return;
          }
          if (!isShortcutMatch(event, binding.combo)) {
              return;
          }

          const editor = editorRef.current;
          const targetNode = resolveEventTargetNode(event.target);
          const editorHasFocus = !!editor?.hasTextFocus?.();
          const inQueryEditor = !!(targetNode && queryEditorRootRef.current?.contains(targetNode));
          if (!editorHasFocus && !inQueryEditor && !isDocumentLevelShortcutTarget(targetNode)) {
              return;
          }

          event.preventDefault();
          event.stopPropagation();
          toggleResultPanelVisibility();
      };

      window.addEventListener('keydown', handleToggleResultsShortcut, true);
      return () => {
          window.removeEventListener('keydown', handleToggleResultsShortcut, true);
      };
  }, [isActive, toggleQueryResultsPanelShortcutBinding, toggleResultPanelVisibility]);

  useEffect(() => {
      const handleSaveActiveQuery = () => {
          if (!isActive) {
              return;
          }
          void handleQuickSave();
      };

      window.addEventListener('gonavi:save-active-query', handleSaveActiveQuery as EventListener);
      return () => {
          window.removeEventListener('gonavi:save-active-query', handleSaveActiveQuery as EventListener);
      };
  }, [isActive, handleQuickSave]);

  useEffect(() => {
      const handleOpenSqlExecutionLog = (event: Event) => {
          const mode = event instanceof CustomEvent && event.detail?.mode === 'open' ? 'open' : 'toggle';
          handleShowSqlExecutionLog(mode);
      };

      window.addEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
      return () => {
          window.removeEventListener('gonavi:show-sql-execution-log', handleOpenSqlExecutionLog as EventListener);
      };
  }, [handleShowSqlExecutionLog]);

  const handleSave = async () => {
      try {
          const values = await saveForm.validateFields();
          const existed = currentSavedQuery || null;
          const fallbackSavedId = String(tab.savedQueryId || '').trim();
          const nextSavedId = existed?.id || fallbackSavedId || `saved-${Date.now()}`;
          await persistQuery({
              id: nextSavedId,
              name: String(values.name || '').trim() || translate('query_editor.save_modal.unnamed'),
              createdAt: existed?.createdAt,
          });
          message.success(translate(
              saveModalMode === 'rename' ? 'query_editor.message.renamed' : 'query_editor.message.saved'
          ));
          setIsSaveModalOpen(false);
      } catch (e) {
          if (e instanceof Error) {
              message.error(translate('query_editor.message.save_query_failed', {
                  error: e.message,
              }));
          }
      }
  };

  const handleCloseResult = (key: string) => {
      setResultSets(prev => {
          const idx = prev.findIndex(r => r.key === key);
          if (idx < 0) return prev;
          const next = prev.filter(r => r.key !== key);

          setActiveResultKey(prevActive => {
              if (prevActive && prevActive !== key) return prevActive;
              return next[idx]?.key || next[idx - 1]?.key || next[0]?.key || '';
          });

          return next;
      });
  };

  const replaceResultSetsAfterMenuClose = (next: ResultSet[], preferredKey?: string) => {
      setResultSets(next);
      setActiveResultKey(prevActive => {
          if (preferredKey && next.some(result => result.key === preferredKey)) return preferredKey;
          if (prevActive && next.some(result => result.key === prevActive)) return prevActive;
          return next[0]?.key || '';
      });
  };

  const closeOtherResultTabs = (key: string) => {
      const target = resultSets.find(result => result.key === key);
      replaceResultSetsAfterMenuClose(target ? [target] : resultSets, key);
  };

  const closeResultTabsToLeft = (key: string) => {
      const index = resultSets.findIndex(result => result.key === key);
      if (index <= 0) return;
      replaceResultSetsAfterMenuClose(resultSets.slice(index), key);
  };

  const closeResultTabsToRight = (key: string) => {
      const index = resultSets.findIndex(result => result.key === key);
      if (index < 0 || index >= resultSets.length - 1) return;
      replaceResultSetsAfterMenuClose(resultSets.slice(0, index + 1), key);
  };

  const closeAllResultTabs = () => {
      setResultSets([]);
      setActiveResultKey('');
  };

  const openResultInWindow = (
      key: string,
      preferred?: { x?: number; y?: number; width?: number; height?: number },
  ) => {
      const target = resultSets.find((result) => result.key === key);
      if (!target) return;
      const index = resultSets.findIndex((result) => result.key === key);
      const title = target.resultType === 'message'
          ? translate('query_editor.results_panel.tab.message', { index: index + 1 })
          : translate('query_editor.results_panel.detached.title', { index: index + 1 });
      const windowId = `query-result:${tab.id}:${target.key}`;
      useStore.getState().detachQueryResultWindow({
          id: windowId,
          sourceQueryTabId: tab.id,
          connectionId: currentConnectionId || tab.connectionId || '',
          // 独立窗也要带上结果表元数据所属库，否则列类型/注释会丢
          dbName: target.metadataDbName || currentDb || tab.dbName || '',
          title,
          ...(preferred?.x !== undefined ? { x: preferred.x } : {}),
          ...(preferred?.y !== undefined ? { y: preferred.y } : {}),
          ...(preferred?.width !== undefined ? { width: preferred.width } : {}),
          ...(preferred?.height !== undefined ? { height: preferred.height } : {}),
          result: {
              key: target.key,
              sql: target.sql,
              exportSql: target.exportSql,
              sourceStatementIndex: target.sourceStatementIndex,
              statementResultIndex: target.statementResultIndex,
              rows: target.rows,
              columns: target.columns,
              messages: target.messages,
              resultType: target.resultType,
              tableName: target.metadataTableName || target.tableName,
              metadataDbName: target.metadataDbName,
              metadataTableName: target.metadataTableName,
              pkColumns: target.pkColumns || [],
              editLocator: target.editLocator as any,
              readOnly: target.readOnly !== false,
              showRowNumberColumn: target.showRowNumberColumn,
              truncated: target.truncated,
          },
      });
      handleCloseResult(key);
  };

  React.useEffect(() => {
      const handleRestoreQueryResult = (event: Event) => {
          const detail = (event as CustomEvent).detail || {};
          const sourceQueryTabId = String(detail.sourceQueryTabId || '').trim();
          if (sourceQueryTabId !== tab.id) return;
          const restored = detail.result;
          if (!restored || typeof restored !== 'object') return;
          const restoredKey = String(restored.key || '').trim();
          if (!restoredKey) return;
          setResultSets((prev) => {
              if (prev.some((item) => item.key === restoredKey)) {
                  return prev;
              }
              return [
                  ...prev,
                  {
                      key: restoredKey,
                      sql: String(restored.sql || ''),
                      exportSql: restored.exportSql,
                      sourceStatementIndex: restored.sourceStatementIndex,
                      statementResultIndex: restored.statementResultIndex,
                      rows: Array.isArray(restored.rows) ? restored.rows : [],
                      columns: Array.isArray(restored.columns) ? restored.columns : [],
                      messages: Array.isArray(restored.messages) ? restored.messages : undefined,
                      resultType: restored.resultType === 'message' ? 'message' : 'grid',
                      tableName: restored.tableName,
                      metadataDbName: restored.metadataDbName,
                      metadataTableName: restored.metadataTableName,
                      pkColumns: Array.isArray(restored.pkColumns) ? restored.pkColumns : [],
                      editLocator: restored.editLocator,
                      readOnly: restored.readOnly !== false,
                      showRowNumberColumn: restored.showRowNumberColumn,
                      truncated: restored.truncated,
                  } as ResultSet,
              ];
          });
          setActiveResultKey(restoredKey);
          updateResultPanelVisibility(true);
      };
      window.addEventListener('gonavi:restore-query-result', handleRestoreQueryResult as EventListener);
      return () => {
          window.removeEventListener('gonavi:restore-query-result', handleRestoreQueryResult as EventListener);
      };
  }, [tab.id, updateResultPanelVisibility]);

  const toggleQueryResultsPanelShortcutLabel =
      toggleQueryResultsPanelShortcutBinding.enabled && toggleQueryResultsPanelShortcutBinding.combo
          ? getShortcutDisplayLabel(toggleQueryResultsPanelShortcutBinding.combo, activeShortcutPlatform)
          : '';

  const handleDiagnoseExecutionError = () => {
      const errSql = getCurrentQuery();
      const prompt = translate('query_editor.ai_prompt.diagnose', {
          sql: errSql,
          error: executionError,
      });
      const store = useStore.getState();
      const wasClosed = !store.aiPanelVisible;
      if (wasClosed) store.setAIPanelVisible(true);
      setTimeout(() => {
          window.dispatchEvent(new CustomEvent('gonavi:ai:inject-prompt', { detail: { prompt } }));
      }, wasClosed ? 350 : 0);
  };

  const sqlEditorTransactionToolbar = (
      <QueryEditorTransactionToolbar
          isV2Ui={isV2Ui}
          darkMode={darkMode}
          transaction={pendingSqlTransaction}
          autoCommitRemainingSeconds={sqlEditorAutoCommitRemainingSeconds}
          onFinish={(action) => void finishPendingSqlTransaction(action, 'manual')}
      />
  );
  const queryEditorStageStyle: React.CSSProperties = isResultPanelVisible
      ? {
          height: editorHeight,
          minHeight: '100px',
      }
      : {
          flex: '1 1 auto',
          minHeight: 0,
      };
  const resolvedQueryEditorStageStyle: React.CSSProperties = isV2Ui
      ? {
          ...queryEditorStageStyle,
      } as React.CSSProperties
      : queryEditorStageStyle;

  return (
    <div ref={queryEditorRootRef} className={isV2Ui ? 'gn-v2-query-editor' : undefined} style={{ flex: '1 1 auto', minHeight: 0, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
      <div
        ref={editorPaneRef}
        className={isV2Ui ? 'gn-v2-query-editor-pane' : undefined}
        style={{ display: 'flex', flexDirection: 'column', minHeight: 0, flex: isResultPanelVisible ? '0 0 auto' : '1 1 auto' }}
      >
      <QueryEditorToolbar
        isV2Ui={isV2Ui}
        currentConnectionId={currentConnectionId}
        currentDb={currentDb}
        queryCapableConnections={queryCapableConnections}
        dbList={dbList}
        maxRows={queryOptions?.maxRows ?? 5000}
        sqlEditorCommitMode={sqlEditorCommitMode}
        sqlEditorAutoCommitDelayMs={sqlEditorAutoCommitDelayMs}
        pendingTransactionToolbar={pendingSqlTransaction ? sqlEditorTransactionToolbar : null}
        runQueryShortcutBinding={runQueryShortcutBinding}
        saveQueryShortcutBinding={saveQueryShortcutBinding}
        formatSqlShortcutBinding={formatSqlShortcutBinding}
        triggerSqlAiCompletionShortcutBinding={triggerSqlAiCompletionShortcutBinding}
        toggleQueryResultsPanelShortcutBinding={toggleQueryResultsPanelShortcutBinding}
        activeShortcutPlatform={activeShortcutPlatform}
        isResultPanelVisible={isResultPanelVisible}
        loading={loading}
        saveMoreMenuItems={saveMoreMenuItems}
        formatSettingsMenu={formatSettingsMenu}
        onConnectionChange={(val) => {
            setCurrentConnectionId(val);
            setCurrentDb('');
        }}
        onDatabaseChange={setCurrentDb}
        onMaxRowsChange={(maxRows) => setQueryOptions({ maxRows })}
        onCommitModeChange={(mode) => setSqlEditorTransactionOptions(
            mode === 'auto'
                ? { commitMode: mode, autoCommitDelayMs: 0 }
                : { commitMode: mode },
        )}
        onAutoCommitDelayMsChange={(delayMs) => setSqlEditorTransactionOptions({ autoCommitDelayMs: delayMs })}
        onCaptureEditorCursorPosition={captureEditorCursorPosition}
        onRun={handleRun}
        onCancel={handleCancel}
        onQuickSave={handleQuickSave}
        onFindInEditor={handleOpenEditorFind}
        onFormat={handleFormat}
        onTriggerSqlAiCompletion={() => triggerAiInlineCompletionRef.current?.()}
        onToggleResultPanelVisibility={toggleResultPanelVisibility}
        onAIAction={handleAIAction}
        showViewDataVerify={
          isObjectEditQueryTab
          && (
            Boolean(String(tab.viewName || '').trim())
            || tab.objectType === 'view'
            || tab.objectType === 'materialized-view'
            || isViewEditSql(query)
          )
        }
        onViewDataVerify={() => {
          const viewName = resolveViewNameForVerify({
            sql: query,
            tabViewName: tab.viewName,
            tabTitle: tab.title,
          });
          if (!viewName) {
            message.warning(translate('result_diff.view_verify.error.no_view_name'));
            return;
          }
          setViewDataVerifyOpen(true);
        }}
      />
      
      <div
        ref={editorStageRef}
        className={isV2Ui ? 'gn-v2-query-monaco-stage gn-query-monaco-stage' : 'gn-query-monaco-stage'}
        style={resolvedQueryEditorStageStyle}
      >
        <div
          ref={editorShellRef}
          className={isV2Ui ? 'gn-v2-query-monaco-shell' : undefined}
          style={isV2Ui ? { flex: '1 1 auto', minHeight: 0 } : undefined}
        >
          <Editor 
            height="100%" 
            gonaviTypography="code"
            defaultLanguage="sql" 
            theme={darkMode ? "transparent-dark" : "transparent-light"}
            defaultValue={query}
            onChange={(val) => {
                const nextValue = val || '';
                syncQueryDraft(nextValue);
            }}
            onMount={handleEditorDidMount}
            options={queryEditorMonacoOptions}
          />
        </div>
      </div>

      {isResultPanelVisible && (
        <div
          className={isV2Ui ? 'gn-v2-query-resizer' : undefined}
          onMouseDown={handleMouseDown}
          style={{
              height: '5px',
              cursor: 'row-resize',
              background: darkMode ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)',
              flexShrink: 0,
              zIndex: 10
          }}
          title={translate('query_editor.action.resize_editor')}
        />
      )}
      </div>

      {isResultPanelVisible && (
        <QueryEditorResultsPanel
          resultSets={resultSets}
          activeResultKey={activeResultKey}
          loading={loading}
          executionError={executionError}
          sqlLogCount={sqlLogCount}
          darkMode={darkMode}
          isV2Ui={isV2Ui}
          currentDb={currentDb}
          currentConnectionId={currentConnectionId}
          toggleShortcutLabel={toggleQueryResultsPanelShortcutLabel}
          onActiveResultKeyChange={setActiveResultKey}
          onHide={() => updateResultPanelVisibility(false)}
          onCloseResult={handleCloseResult}
          onCloseOtherResultTabs={closeOtherResultTabs}
          onCloseResultTabsToLeft={closeResultTabsToLeft}
          onCloseResultTabsToRight={closeResultTabsToRight}
          onCloseAllResultTabs={closeAllResultTabs}
          onOpenResultInWindow={openResultInWindow}
          onReloadResult={handleReloadResult}
          onResultPageChange={handleResultPageChange}
          onDiagnoseExecutionError={handleDiagnoseExecutionError}
          onCompareResult={(resultKey) => {
            setResultDiffAnchorKey(resultKey);
            setResultDiffWizardOpen(true);
          }}
        />
      )}

      <ResultDiffWizard
        open={resultDiffWizardOpen}
        results={resultSets
          .map((rs, idx) => ({ rs, idx }))
          .filter(({ rs }) => rs.resultType !== 'message' && Array.isArray(rs.columns) && rs.columns.length > 0)
          .map(({ rs, idx }): ResultDiffComparableResult => ({
            key: rs.key,
            label: translate('query_editor.results_panel.tab.result', { index: idx + 1 }) + ` (${rs.rows?.length ?? 0})`,
            sql: String(rs.sql || rs.exportSql || ''),
            columns: rs.columns || [],
            rows: (rs.rows || []) as Record<string, unknown>[],
            pkColumns: rs.pkColumns || [],
            truncated: Boolean(rs.truncated),
            metadataDbName: rs.metadataDbName || currentDb,
            metadataTableName: rs.metadataTableName || rs.tableName,
          }))}
        initialRightKey={resultDiffAnchorKey}
        connectionConfig={(() => {
          const conn = connections.find((c) => c.id === currentConnectionId);
          return conn ? buildRpcConnectionConfig(conn) : {};
        })()}
        database={currentDb}
        onCancel={() => setResultDiffWizardOpen(false)}
        onCompleted={(payload) => {
          setResultDiffWizardOpen(false);
          setResultDiffSession(payload);
        }}
      />

      {resultDiffSession && (
        <ResultDiffPanel
          open={Boolean(resultDiffSession)}
          jobId={resultDiffSession.jobId}
          summary={resultDiffSession.summary}
          leftLabel={resultDiffSession.leftLabel}
          rightLabel={resultDiffSession.rightLabel}
          darkMode={darkMode}
          columnMeta={resultDiffSession.columnMeta}
          onClose={() => setResultDiffSession(null)}
        />
      )}

      <ViewDataVerifyWizard
        open={viewDataVerifyOpen}
        connectionConfig={(() => {
          const conn = connections.find((c) => c.id === currentConnectionId);
          return conn ? buildRpcConnectionConfig(conn) : {};
        })()}
        database={currentDb}
        dbType={String(connections.find((c) => c.id === currentConnectionId)?.config?.type || '')}
        viewName={resolveViewNameForVerify({
          sql: query,
          tabViewName: tab.viewName,
          tabTitle: tab.title,
        })}
        ddlSql={query}
        onCancel={() => setViewDataVerifyOpen(false)}
        onCompleted={(payload) => {
          setViewDataVerifyOpen(false);
          setResultDiffSession(payload);
        }}
      />

      <Modal
        title={translate('query_editor.text_to_sql.title')}
        open={isTextToSqlModalOpen}
        centered
        mask={false}
        maskClosable={!textToSqlGenerating}
        width={640}
        draggable
        resizable
        minResizableWidth={480}
        minResizableHeight={320}
        onCancel={() => {
          if (!textToSqlGenerating) {
            setIsTextToSqlModalOpen(false);
          }
        }}
        footer={[
          <Button key="cancel" disabled={textToSqlGenerating} onClick={() => setIsTextToSqlModalOpen(false)}>
            {translate('common.cancel')}
          </Button>,
          <Button key="generate" type="primary" loading={textToSqlGenerating} onClick={handleGenerateTextToSql}>
            {translate('query_editor.text_to_sql.generate')}
          </Button>,
        ]}
        styles={{
          content: {
            borderRadius: 16,
            border: darkMode ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(15,23,42,0.12)',
            background: darkMode ? 'rgba(18,18,20,0.98)' : 'rgba(255,255,255,0.98)',
            boxShadow: darkMode ? '0 24px 60px rgba(0,0,0,0.45)' : '0 24px 60px rgba(15,23,42,0.16)',
            backdropFilter: 'blur(12px)',
          },
          header: {
            background: 'transparent',
            borderBottom: 'none',
            paddingBottom: 8,
          },
          body: {
            paddingTop: 8,
            paddingBottom: 16,
          },
        }}
      >
        <div
          data-query-editor-text-to-sql-modal="true"
          style={{ display: 'flex', flexDirection: 'column', gap: 14 }}
        >
          <div style={{ fontSize: 12, lineHeight: 1.6, color: darkMode ? 'rgba(255,255,255,0.65)' : 'rgba(16,24,40,0.6)' }}>
            {translate('query_editor.text_to_sql.description')}
          </div>
          <Input.TextArea
            autoFocus
            value={textToSqlInstruction}
            onChange={(event) => setTextToSqlInstruction(event.target.value)}
            placeholder={translate('query_editor.text_to_sql.placeholder')}
            autoSize={{ minRows: 5, maxRows: 10 }}
            disabled={textToSqlGenerating}
          />
          <Segmented
            value={textToSqlApplyMode}
            onChange={(value) => setTextToSqlApplyMode(value as QueryEditorAiApplyMode)}
            disabled={textToSqlGenerating}
            options={[
              { label: translate('query_editor.text_to_sql.mode.insert'), value: 'insert' },
              { label: translate('query_editor.text_to_sql.mode.replace_selection'), value: 'replaceSelection' },
              { label: translate('query_editor.text_to_sql.mode.replace_all'), value: 'replaceAll' },
            ]}
          />
        </div>
      </Modal>

      <Modal
        title={translate('query_editor.snippet_picker.title')}
        open={isSqlSnippetPickerOpen}
        centered
        mask={false}
        maskClosable={false}
        width={620}
        draggable
        resizable
        minResizableWidth={460}
        minResizableHeight={320}
        onCancel={handleCloseSqlSnippetPicker}
        footer={null}
        styles={{
          content: {
            borderRadius: 16,
            border: darkMode ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(15,23,42,0.12)',
            background: darkMode ? 'rgba(18,18,20,0.98)' : 'rgba(255,255,255,0.98)',
            boxShadow: darkMode ? '0 24px 60px rgba(0,0,0,0.45)' : '0 24px 60px rgba(15,23,42,0.16)',
            backdropFilter: 'blur(12px)',
          },
          header: {
            background: 'transparent',
            borderBottom: 'none',
            paddingBottom: 8,
          },
          body: {
            paddingTop: 8,
            paddingBottom: 16,
            display: 'flex',
            flexDirection: 'column',
            minHeight: 0,
            overflow: 'hidden',
          },
        }}
      >
        <div
          data-query-editor-snippet-picker="true"
          style={{
            display: 'flex',
            flexDirection: 'column',
            gap: 12,
            flex: '1 1 420px',
            minHeight: 0,
            overflow: 'hidden',
          }}
        >
          <div style={{ fontSize: 12, lineHeight: 1.6, color: darkMode ? 'rgba(255,255,255,0.65)' : 'rgba(16,24,40,0.6)' }}>
            {translate('query_editor.snippet_picker.description')}
          </div>
          <Input
            autoFocus
            data-query-editor-snippet-search="true"
            value={sqlSnippetPickerKeyword}
            onChange={(event) => setSqlSnippetPickerKeyword(event.target.value)}
            onPressEnter={() => {
              if (filteredSqlSnippets[0]) {
                handleInsertSqlSnippet(filteredSqlSnippets[0]);
              }
            }}
            placeholder={translate('query_editor.snippet_picker.search_placeholder')}
          />
          <div
            style={{
              flex: '1 1 auto',
              minHeight: 0,
              overflowY: 'auto',
              paddingRight: 4,
              display: 'grid',
              gap: 8,
            }}
          >
            {filteredSqlSnippets.map((snippet) => {
              const preview = String(snippet.description || snippet.syntaxHelp || snippet.body || '')
                .replace(/\s+/g, ' ')
                .trim();
              return (
                <button
                  key={snippet.id}
                  type="button"
                  data-query-editor-snippet-item={snippet.id}
                  onClick={() => handleInsertSqlSnippet(snippet)}
                  style={{
                    textAlign: 'left',
                    borderRadius: 12,
                    border: darkMode ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(15,23,42,0.1)',
                    background: darkMode ? 'rgba(255,255,255,0.03)' : '#fff',
                    padding: '12px 14px',
                    cursor: 'pointer',
                  }}
                >
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 6 }}>
                    <span style={{ fontFamily: 'var(--gn-font-mono)', fontSize: 12, fontWeight: 700, color: '#1677ff' }}>
                      {snippet.prefix}
                    </span>
                    <span style={{ fontSize: 13, fontWeight: 600, color: darkMode ? 'rgba(255,255,255,0.9)' : 'rgba(15,23,42,0.88)' }}>
                      {snippet.name}
                    </span>
                    {snippet.isBuiltin ? (
                      <span
                        style={{
                          fontSize: 11,
                          padding: '1px 8px',
                          borderRadius: 999,
                          background: darkMode ? 'rgba(22,119,255,0.18)' : 'rgba(22,119,255,0.1)',
                          color: '#1677ff',
                        }}
                      >
                        {translate('snippet_settings.tag.builtin')}
                      </span>
                    ) : null}
                  </div>
                  <div
                    style={{
                      fontSize: 12,
                      lineHeight: 1.6,
                      color: darkMode ? 'rgba(255,255,255,0.65)' : 'rgba(16,24,40,0.6)',
                      fontFamily: preview.includes('${') ? 'var(--gn-font-mono)' : undefined,
                    }}
                  >
                    {preview}
                  </div>
                </button>
              );
            })}
            {!filteredSqlSnippets.length ? (
              <div
                data-query-editor-snippet-empty="true"
                style={{
                  borderRadius: 12,
                  padding: '18px 16px',
                  border: darkMode ? '1px dashed rgba(255,255,255,0.14)' : '1px dashed rgba(15,23,42,0.12)',
                  color: darkMode ? 'rgba(255,255,255,0.6)' : 'rgba(16,24,40,0.55)',
                  fontSize: 13,
                  lineHeight: 1.7,
                }}
              >
                {sqlSnippetPickerEmptyLabel}
              </div>
            ) : null}
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
            <Button onClick={handleOpenSnippetSettingsFromPicker}>
              {translate('query_editor.snippet_picker.manage')}
            </Button>
            <Button onClick={handleCloseSqlSnippetPicker}>
              {translate('common.cancel')}
            </Button>
          </div>
        </div>
      </Modal>

      <Modal 
        title={translate(saveModalMode === 'rename' ? 'query_editor.save_modal.rename_title' : 'query_editor.save_modal.title')}
        open={isSaveModalOpen} 
        onOk={handleSave} 
        onCancel={() => setIsSaveModalOpen(false)}
        okText={translate(saveModalMode === 'rename' ? 'query_editor.save_modal.rename_ok' : 'common.save')}
        cancelText={translate('common.cancel')}
      >
          <Form form={saveForm} layout="vertical">
              <Form.Item name="name" label={translate('query_editor.save_modal.name_label')} rules={[{ required: true, message: translate('query_editor.save_modal.name_required') }]}>
                  <Input placeholder={translate('query_editor.save_modal.name_placeholder')} />
              </Form.Item>
          </Form>
      </Modal>

    </div>
    );
};

const setQueryEditorMouseCursor = (
    editor: any,
    cursor: '' | 'pointer',
) => {
    const domNode = editor?.getDomNode?.();
    if (domNode?.style) {
        domNode.style.cursor = cursor;
    }
};

export default React.memo(QueryEditor);
