import React, { useCallback, useEffect, useMemo, useState } from 'react';
import {
  Alert,
  Box,
  Button,
  CircularProgress,
  Checkbox,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Divider,
  FormControl,
  FormControlLabel,
  IconButton,
  InputLabel,
  List,
  ListItemButton,
  ListItemText,
  MenuItem,
  Paper,
  Select,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TablePagination,
  TableRow,
  TextField,
  Tooltip,
  Typography,
} from '@mui/material';
import {
  AddLink,
  DeleteOutline,
  Link as LinkIcon,
  Refresh,
  TableChart,
} from '@mui/icons-material';
import {
  dbExplorerApi,
  DbExplorerColumn,
  DbExplorerConnection,
  DbExplorerRowsResponse,
  DbExplorerTable,
} from '../../services/dbExplorerApi';

const DEFAULT_PAGE_SIZE = 50;

function isMysqlLinkFailure(msg: string | null | undefined): boolean {
  if (!msg) return false;
  const m = msg.toLowerCase();
  return (
    m.includes('communications link failure') ||
    m.includes('not received any packets') ||
    m.includes('connection refused')
  );
}

function formatCell(v: unknown): string {
  if (v === null || v === undefined) return '';
  if (typeof v === 'object') {
    try {
      return JSON.stringify(v);
    } catch {
      return String(v);
    }
  }
  return String(v);
}

/** jdbc:mysql://host:3306/dbname?... → dbname */
function jdbcDatabaseName(jdbc: string | undefined): string {
  if (!jdbc) return '—';
  const m = jdbc.match(/\/\/[^/]+\/([^/?]+)/);
  return m ? decodeURIComponent(m[1]) : '—';
}

const DashboardDbExplorerTab: React.FC = () => {
  const [connections, setConnections] = useState<DbExplorerConnection[]>([]);
  const [activeConnId, setActiveConnId] = useState<string>('default');
  const [tables, setTables] = useState<DbExplorerTable[]>([]);
  const [selectedTable, setSelectedTable] = useState<string | null>(null);
  const [columns, setColumns] = useState<DbExplorerColumn[]>([]);
  const [rowsBody, setRowsBody] = useState<DbExplorerRowsResponse | null>(null);
  const [page, setPage] = useState(0);
  const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
  const [sortColumn, setSortColumn] = useState<string>('');
  const [sortDir, setSortDir] = useState<'ASC' | 'DESC'>('ASC');
  const [searchColumn, setSearchColumn] = useState<string>('');
  const [searchText, setSearchText] = useState('');
  /** 서버로 전달 중인 검색(적용 버튼으로만 갱신) */
  const [appliedSearchColumn, setAppliedSearchColumn] = useState<string>('');
  const [appliedSearchText, setAppliedSearchText] = useState('');
  const [selectedCell, setSelectedCell] = useState<{ column: string; value: unknown } | null>(null);

  const [loadConnErr, setLoadConnErr] = useState<string | null>(null);
  const [tablesErr, setTablesErr] = useState<string | null>(null);
  const [rowsErr, setRowsErr] = useState<string | null>(null);
  /** 테이블 목록 로딩 (행 조회와 분리 — Tree/List가 사라지는 현상 방지) */
  const [tablesLoading, setTablesLoading] = useState(false);
  const [rowsBusy, setRowsBusy] = useState(false);

  const [dialogOpen, setDialogOpen] = useState(false);
  const [formLabel, setFormLabel] = useState('로컬 MySQL');
  const [formHost, setFormHost] = useState('127.0.0.1');
  const [formPort, setFormPort] = useState('3306');
  const [formDb, setFormDb] = useState('stockAnalyzer');
  const [formUser, setFormUser] = useState('stock');
  const [formPass, setFormPass] = useState('');
  const [formSsl, setFormSsl] = useState(false);
  const [formApk, setFormApk] = useState(true);
  const [formErr, setFormErr] = useState<string | null>(null);

  const refreshConnections = useCallback(async () => {
    setLoadConnErr(null);
    try {
      const list = await dbExplorerApi.listConnections();
      setConnections(list);
      setActiveConnId((prev) => (list.some((c) => c.id === prev) ? prev : 'default'));
    } catch (e) {
      setLoadConnErr(dbExplorerApi.errMessage(e));
    }
  }, []);

  useEffect(() => {
    void refreshConnections();
    // 최초 연결 목록만 로드 (refreshConnections는 안정 참조)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const loadTables = useCallback(async () => {
    setTablesErr(null);
    setTablesLoading(true);
    setTables([]);
    setSelectedTable(null);
    setRowsBody(null);
    setColumns([]);
    setAppliedSearchColumn('');
    setAppliedSearchText('');
    try {
      const t = await dbExplorerApi.listTables(activeConnId);
      setTables(t);
    } catch (e) {
      setTablesErr(dbExplorerApi.errMessage(e));
    } finally {
      setTablesLoading(false);
    }
  }, [activeConnId]);

  useEffect(() => {
    void loadTables();
  }, [loadTables]);

  const loadRows = useCallback(async () => {
    if (!selectedTable) return;
    setRowsErr(null);
    setRowsBusy(true);
    try {
      const st = appliedSearchText.trim();
      const sc = st ? appliedSearchColumn || undefined : undefined;
      const body = await dbExplorerApi.fetchRows(activeConnId, selectedTable, {
        page,
        size: pageSize,
        sortColumn: sortColumn || undefined,
        sortDir,
        searchColumn: sc,
        search: st,
      });
      setRowsBody(body);
      if (body.columnNames?.length && !sortColumn) {
        setSortColumn(body.columnNames[0]);
      }
    } catch (e) {
      setRowsErr(dbExplorerApi.errMessage(e));
      setRowsBody(null);
    } finally {
      setRowsBusy(false);
    }
  }, [activeConnId, selectedTable, page, pageSize, sortColumn, sortDir, appliedSearchColumn, appliedSearchText]);

  useEffect(() => {
    if (!selectedTable) return;
    let cancelled = false;
    (async () => {
      try {
        const cols = await dbExplorerApi.listColumns(activeConnId, selectedTable);
        if (!cancelled) {
          setColumns(cols);
          if (cols.length && !searchColumn) {
            setSearchColumn(cols[0].name);
          }
        }
      } catch {
        if (!cancelled) setColumns([]);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [activeConnId, selectedTable]);

  useEffect(() => {
    if (!selectedTable) return;
    void loadRows();
  }, [selectedTable, loadRows]);

  const activeConn = useMemo(
    () => connections.find((c) => c.id === activeConnId),
    [connections, activeConnId]
  );

  /** 그리드 헤더: 행 응답 전에는 메타 API 컬럼명으로 표시 */
  const dataGridColumns = useMemo(() => {
    if (!selectedTable) return [] as string[];
    if (rowsBody?.columnNames?.length) return rowsBody.columnNames;
    return columns.map((c) => c.name);
  }, [selectedTable, rowsBody, columns]);

  const openNewConnection = () => {
    setFormErr(null);
    setDialogOpen(true);
  };

  const submitConnect = async () => {
    setFormErr(null);
    const port = parseInt(formPort, 10);
    if (!formLabel.trim() || !formHost.trim() || !formDb.trim() || !formUser.trim()) {
      setFormErr('이름·호스트·DB·사용자는 필수입니다.');
      return;
    }
    if (Number.isNaN(port) || port < 1 || port > 65535) {
      setFormErr('포트는 1~65535 숫자여야 합니다.');
      return;
    }
    try {
      const created = await dbExplorerApi.connect({
        label: formLabel.trim(),
        host: formHost.trim(),
        port,
        database: formDb.trim(),
        username: formUser.trim(),
        password: formPass,
        useSsl: formSsl,
        allowPublicKeyRetrieval: formApk,
      });
      setDialogOpen(false);
      setFormPass('');
      await refreshConnections();
      setActiveConnId(created.id);
    } catch (e) {
      setFormErr(dbExplorerApi.errMessage(e));
    }
  };

  const disconnectOne = async (id: string) => {
    if (id === 'default') return;
    try {
      await dbExplorerApi.disconnect(id);
      await refreshConnections();
      if (activeConnId === id) {
        setActiveConnId('default');
      }
    } catch (e) {
      setLoadConnErr(dbExplorerApi.errMessage(e));
    }
  };

  const selectTable = (name: string) => {
    setSelectedTable(name);
    setRowsBody(null);
    setColumns([]);
    setPage(0);
    setSelectedCell(null);
    setAppliedSearchColumn('');
    setAppliedSearchText('');
  };

  const exportTsv = () => {
    if (!rowsBody?.columnNames?.length) return;
    const header = rowsBody.columnNames.join('\t');
    const lines = rowsBody.rows.map((row) =>
      rowsBody.columnNames.map((c) => formatCell(row[c]).replace(/\t/g, ' ')).join('\t')
    );
    const blob = new Blob([header + '\n' + lines.join('\n')], { type: 'text/tab-separated-values;charset=utf-8' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = `${selectedTable || 'export'}.tsv`;
    a.click();
    URL.revokeObjectURL(a.href);
  };

  return (
    <Box>
      <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
        DBeaver처럼 여러 MySQL 연결을 등록할 수 있습니다. <strong>새 연결</strong>은 백엔드에 일시적 커넥션 풀을 만들며,
        유휴 시 자동 종료됩니다. 비밀번호는 브라우저에 저장하지 않습니다.
      </Typography>

      {loadConnErr && (
        <Alert severity="error" sx={{ mb: 2 }} onClose={() => setLoadConnErr(null)}>
          {loadConnErr}
        </Alert>
      )}
      {tablesErr && (
        <Alert severity="warning" sx={{ mb: 2 }} onClose={() => setTablesErr(null)}>
          {tablesErr}
        </Alert>
      )}
      {rowsErr && (
        <Alert severity="warning" sx={{ mb: 2 }} onClose={() => setRowsErr(null)}>
          {rowsErr}
        </Alert>
      )}

      <Box
        sx={{
          display: 'flex',
          flexDirection: 'column',
          gap: 1.5,
          minHeight: { xs: 480, md: 'calc(100vh - 260px)' },
          maxHeight: { md: 'calc(100vh - 200px)' },
        }}
      >
        <Box sx={{ display: 'flex', flex: 1, gap: 1.5, minHeight: 0, minWidth: 0, flexDirection: { xs: 'column', md: 'row' } }}>
          {/* 좌측: DB 클라이언트 네비게이터 — 연결 + 현재 DB + 테이블 목록 */}
          <Paper
            sx={{
              width: { xs: '100%', md: 320 },
              maxWidth: '100%',
              flexShrink: 0,
              p: 1.5,
              display: 'flex',
              flexDirection: 'column',
              minHeight: { xs: 320, md: 0 },
              maxHeight: { md: '100%' },
              overflow: 'hidden',
            }}
          >
            <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
              <Typography variant="subtitle2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
                <LinkIcon fontSize="small" /> 연결
              </Typography>
              <Tooltip title="연결 목록 새로고침">
                <IconButton size="small" onClick={() => void refreshConnections()} disabled={tablesLoading}>
                  <Refresh fontSize="small" />
                </IconButton>
              </Tooltip>
            </Box>
            <Button variant="contained" size="small" startIcon={<AddLink />} onClick={openNewConnection} sx={{ mb: 1 }}>
              새 연결
            </Button>
            <List dense disablePadding sx={{ maxHeight: { xs: 200, md: '36%' }, flexShrink: 0, overflow: 'auto', mb: 1 }}>
              {connections.map((c) => (
                <Box key={c.id} sx={{ display: 'flex', alignItems: 'stretch' }}>
                  <ListItemButton
                    selected={c.id === activeConnId}
                    onClick={() => {
                      setActiveConnId(c.id);
                      setSelectedTable(null);
                    }}
                    sx={{ flex: 1, py: 0.5 }}
                  >
                    <ListItemText
                      primary={c.label}
                      secondary={c.kind}
                      primaryTypographyProps={{ variant: 'body2', noWrap: true }}
                      secondaryTypographyProps={{ variant: 'caption' }}
                    />
                  </ListItemButton>
                  {c.kind === 'CUSTOM' && (
                    <Tooltip title="연결 끊기">
                      <IconButton
                        size="small"
                        sx={{ alignSelf: 'center' }}
                        onClick={(ev) => {
                          ev.stopPropagation();
                          void disconnectOne(c.id);
                        }}
                      >
                        <DeleteOutline fontSize="small" />
                      </IconButton>
                    </Tooltip>
                  )}
                </Box>
              ))}
            </List>

            <Divider sx={{ my: 1 }} />

            {activeConn && (
              <Box sx={{ mb: 1 }}>
                <Typography variant="caption" color="text.secondary" display="block">
                  현재 데이터베이스
                </Typography>
                <Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-all' }}>
                  {jdbcDatabaseName(activeConn.jdbcMasked)}
                </Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5, wordBreak: 'break-all' }}>
                  {activeConn.jdbcMasked}
                </Typography>
              </Box>
            )}

            <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5 }}>
              <Typography variant="subtitle2" sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
                <TableChart fontSize="small" /> 테이블 ({tables.length})
              </Typography>
              <Tooltip title="테이블 목록 다시 불러오기">
                <span>
                  <IconButton size="small" disabled={tablesLoading} onClick={() => void loadTables()}>
                    {tablesLoading ? <CircularProgress size={18} /> : <Refresh fontSize="small" />}
                  </IconButton>
                </span>
              </Tooltip>
            </Box>

            <Box sx={{ flex: 1, minHeight: 120, overflow: 'auto', border: 1, borderColor: 'divider', borderRadius: 1 }}>
              {tablesLoading ? (
                <Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
                  <CircularProgress size={28} />
                </Box>
              ) : tables.length === 0 ? (
                <Typography variant="body2" color="text.secondary" sx={{ p: 2 }}>
                  테이블이 없거나 스키마를 읽을 수 없습니다. 위 새로고침을 눌러 보세요.
                </Typography>
              ) : (
                <List dense disablePadding>
                  {tables.map((t) => (
                    <ListItemButton
                      key={t.name}
                      selected={selectedTable === t.name}
                      onClick={() => selectTable(t.name)}
                      sx={{ py: 0.75, alignItems: 'flex-start' }}
                    >
                      <ListItemText
                        primary={t.name}
                        secondary={
                          t.approxSizeKb != null || t.tableRowsApprox != null
                            ? `${t.approxSizeKb != null ? `${t.approxSizeKb} KB` : ''}${
                                t.tableRowsApprox != null ? ` · 약 ${Number(t.tableRowsApprox).toLocaleString()}행` : ''
                              }`
                            : undefined
                        }
                        primaryTypographyProps={{ variant: 'body2', fontFamily: 'monospace', fontSize: '0.8rem' }}
                        secondaryTypographyProps={{ variant: 'caption' }}
                      />
                    </ListItemButton>
                  ))}
                </List>
              )}
            </Box>
          </Paper>

          {/* 우측: 선택 테이블 데이터 그리드 */}
          <Paper
            sx={{
              flex: 1,
              minWidth: 0,
              p: 1.5,
              display: 'flex',
              flexDirection: 'column',
              minHeight: { xs: 400, md: 0 },
              maxHeight: { md: '100%' },
              overflow: 'hidden',
            }}
          >
            <Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 1, mb: 1, flexShrink: 0 }}>
              <Typography variant="subtitle1" sx={{ flex: '1 1 auto', minWidth: 0 }} noWrap>
                {selectedTable
                  ? `${jdbcDatabaseName(activeConn?.jdbcMasked)} · ${selectedTable}`
                  : '테이블을 왼쪽에서 선택하세요'}
              </Typography>
              <Button size="small" variant="outlined" disabled={!rowsBody} onClick={exportTsv}>
                TSV보내기
              </Button>
              <Button
                size="small"
                variant="outlined"
                startIcon={<Refresh />}
                disabled={!selectedTable || rowsBusy}
                onClick={() => void loadRows()}
              >
                데이터 새로고침
              </Button>
              {rowsBusy && <CircularProgress size={22} sx={{ ml: 0.5 }} />}
            </Box>

            {!selectedTable ? (
              <Box
                sx={{
                  flex: 1,
                  display: 'flex',
                  alignItems: 'center',
                  justifyContent: 'center',
                  color: 'text.secondary',
                  border: 1,
                  borderColor: 'divider',
                  borderRadius: 1,
                  borderStyle: 'dashed',
                }}
              >
                <Typography variant="body2">왼쪽 목록에서 테이블을 선택하면 이 영역에 행 데이터가 표시됩니다.</Typography>
              </Box>
            ) : (
              <>
                <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 1, alignItems: 'center', flexShrink: 0 }}>
                  <FormControl size="small" sx={{ minWidth: 140 }}>
                    <InputLabel>정렬 컬럼</InputLabel>
                    <Select
                      label="정렬 컬럼"
                      value={sortColumn || (columns[0]?.name ?? '')}
                      onChange={(e) => setSortColumn(String(e.target.value))}
                    >
                      {columns.map((c) => (
                        <MenuItem key={c.name} value={c.name}>
                          {c.name}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                  <FormControl size="small" sx={{ minWidth: 100 }}>
                    <InputLabel>순서</InputLabel>
                    <Select label="순서" value={sortDir} onChange={(e) => setSortDir(e.target.value as 'ASC' | 'DESC')}>
                      <MenuItem value="ASC">ASC</MenuItem>
                      <MenuItem value="DESC">DESC</MenuItem>
                    </Select>
                  </FormControl>
                  <FormControl size="small" sx={{ minWidth: 140 }}>
                    <InputLabel>검색 컬럼</InputLabel>
                    <Select
                      label="검색 컬럼"
                      value={searchColumn || (columns[0]?.name ?? '')}
                      onChange={(e) => setSearchColumn(String(e.target.value))}
                    >
                      {columns.map((c) => (
                        <MenuItem key={c.name} value={c.name}>
                          {c.name}
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                  <TextField
                    size="small"
                    label="검색 (LIKE)"
                    value={searchText}
                    onChange={(e) => setSearchText(e.target.value)}
                    placeholder="값 일부 입력"
                    sx={{ flex: '1 1 200px', minWidth: 160 }}
                  />
                  <Button
                    size="small"
                    variant="contained"
                    disabled={rowsBusy}
                    onClick={() => {
                      setAppliedSearchColumn(searchColumn);
                      setAppliedSearchText(searchText.trim());
                      setPage(0);
                    }}
                  >
                    검색 적용
                  </Button>
                </Box>

                <TableContainer
                  sx={{
                    flex: 1,
                    minHeight: 200,
                    overflow: 'auto',
                    border: 1,
                    borderColor: 'divider',
                    borderRadius: 1,
                  }}
                >
                  <Table size="small" stickyHeader>
                    <TableHead>
                      <TableRow>
                        {(dataGridColumns.length ? dataGridColumns : ['…']).map((col) => (
                          <TableCell key={col} sx={{ fontWeight: 700, whiteSpace: 'nowrap', bgcolor: 'background.paper' }}>
                            {col}
                          </TableCell>
                        ))}
                      </TableRow>
                    </TableHead>
                    <TableBody>
                      {rowsBusy && !rowsBody ? (
                        <TableRow>
                          <TableCell
                            colSpan={Math.max(dataGridColumns.length || 1, 1)}
                            align="center"
                            sx={{ py: 6 }}
                          >
                            <CircularProgress size={28} />
                          </TableCell>
                        </TableRow>
                      ) : !rowsBody || !rowsBody.columnNames?.length ? (
                        <TableRow>
                          <TableCell
                            colSpan={Math.max(dataGridColumns.length || 1, 1)}
                            align="center"
                            sx={{ py: 4 }}
                          >
                            <Typography color="text.secondary">데이터를 불러오지 못했습니다.</Typography>
                          </TableCell>
                        </TableRow>
                      ) : rowsBody.rows.length === 0 ? (
                        <TableRow>
                          <TableCell colSpan={Math.max(rowsBody.columnNames.length, 1)} align="center" sx={{ py: 4 }}>
                            <Typography color="text.secondary">조건에 맞는 행이 없습니다.</Typography>
                          </TableCell>
                        </TableRow>
                      ) : (
                        rowsBody.rows.map((row, ri) => (
                          <TableRow key={ri} hover>
                            {rowsBody.columnNames.map((col) => (
                              <TableCell
                                key={col}
                                onClick={() => setSelectedCell({ column: col, value: row[col] })}
                                sx={{
                                  maxWidth: 220,
                                  overflow: 'hidden',
                                  textOverflow: 'ellipsis',
                                  whiteSpace: 'nowrap',
                                  cursor: 'pointer',
                                  bgcolor:
                                    selectedCell?.column === col && selectedCell?.value === row[col]
                                      ? 'action.selected'
                                      : undefined,
                                }}
                              >
                                {formatCell(row[col])}
                              </TableCell>
                            ))}
                          </TableRow>
                        ))
                      )}
                    </TableBody>
                  </Table>
                </TableContainer>

                {rowsBody && (
                  <TablePagination
                    component="div"
                    sx={{ flexShrink: 0, borderTop: 1, borderColor: 'divider' }}
                    count={rowsBody.total}
                    page={page}
                    onPageChange={(_, p) => setPage(p)}
                    rowsPerPage={pageSize}
                    onRowsPerPageChange={(e) => {
                      setPageSize(parseInt(e.target.value, 10));
                      setPage(0);
                    }}
                    rowsPerPageOptions={[25, 50, 100, 200]}
                    labelRowsPerPage="행 수"
                    labelDisplayedRows={({ from, to, count }) => `${from}-${to} / ${count}`}
                  />
                )}

                {selectedCell && (
                  <Box sx={{ mt: 1, p: 1, bgcolor: 'action.hover', borderRadius: 1, flexShrink: 0 }}>
                    <Typography variant="caption" color="text.secondary">
                      선택 셀: {selectedCell.column}
                    </Typography>
                    <Typography
                      component="pre"
                      variant="body2"
                      sx={{ m: 0, mt: 0.5, whiteSpace: 'pre-wrap', wordBreak: 'break-all', fontFamily: 'monospace' }}
                    >
                      {formatCell(selectedCell.value)}
                    </Typography>
                  </Box>
                )}
              </>
            )}
          </Paper>
        </Box>
      </Box>

      <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
        <DialogTitle>새 MySQL 연결</DialogTitle>
        <DialogContent dividers>
          {formErr && (
            <Alert severity="error" sx={{ mb: 2 }}>
              {formErr}
            </Alert>
          )}
          {formErr && isMysqlLinkFailure(formErr) && (
            <Alert severity="warning" sx={{ mb: 2 }}>
              <Typography variant="body2" component="div" sx={{ fontWeight: 600, mb: 0.5 }}>
                네트워크로 MySQL에 닿지 못했을 때 흔한 원인
              </Typography>
              <Box component="ul" sx={{ m: 0, pl: 2.5, mb: 0, '& li': { mb: 0.75 } }}>
                <Box component="li">
                  백엔드가 <strong>Docker</strong>이고 DB도 같은 <code>docker compose</code>의 MySQL이면 호스트는{' '}
                  <code>127.0.0.1</code>이 아니라 서비스 이름 <code>mysql</code>, 포트 <code>3306</code>입니다.
                </Box>
                <Box component="li">
                  백엔드는 Docker인데 MySQL은 <strong>맥/윈도우에 직접 설치</strong>된 경우 → <code>host.docker.internal:3306</code>
                  (프리셋). Linux는 compose에 <code>extra_hosts: host.docker.internal:host-gateway</code>가 있어야 할 수 있습니다.
                </Box>
                <Box component="li">
                  <strong>원격 서버</strong>(예: exdev) MySQL이면 방화벽·보안그룹에서 <strong>백엔드가 붙는 쪽 IP</strong>에서 3306이
                  열려 있어야 합니다. PC 브라우저가 아니라 <strong>백엔드 프로세스 위치</strong> 기준입니다.
                </Box>
                <Box component="li">
                  해당 호스트·포트에 MySQL이 떠 있는지 터미널에서 <code>nc -zv 호스트 3306</code> 등으로 확인해 보세요.
                </Box>
              </Box>
            </Alert>
          )}
          <Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
            프리셋은 입력란만 채웁니다. 비밀번호는 직접 입력하세요.
          </Typography>
          <Alert severity="info" sx={{ mb: 2 }}>
            백엔드가 <strong>Docker 컨테이너</strong>에서 돌면 <code>127.0.0.1</code>은 PC가 아니라 컨테이너 안입니다. PC의
            MySQL이면 <strong>host.docker.internal</strong> 프리셋을 쓰거나, 백엔드를 IDE에서 실행한 뒤{' '}
            <code>127.0.0.1</code>을 사용하세요.
          </Alert>
          <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2 }}>
            <Button
              size="small"
              variant="outlined"
              onClick={() => {
                setFormHost('127.0.0.1');
                setFormPort('3306');
                setFormDb('stockAnalyzer');
                setFormLabel('로컬 127.0.0.1');
              }}
            >
              로컬 127.0.0.1
            </Button>
            <Button
              size="small"
              variant="outlined"
              onClick={() => {
                setFormHost('host.docker.internal');
                setFormPort('3306');
                setFormDb('stockAnalyzer');
                setFormLabel('Docker→호스트 MySQL');
              }}
            >
              host.docker.internal
            </Button>
            <Button
              size="small"
              variant="outlined"
              onClick={() => {
                setFormHost('exdev.co.kr');
                setFormPort('3306');
                setFormDb('stockAnalyzer');
                setFormLabel('서버 exdev.co.kr');
              }}
            >
              exdev.co.kr
            </Button>
            <Button
              size="small"
              variant="outlined"
              onClick={() => {
                setFormHost('mysql');
                setFormPort('3306');
                setFormDb('stockAnalyzer');
                setFormLabel('Compose 서비스 mysql');
              }}
            >
              mysql (컨테이너 내부)
            </Button>
          </Box>
          <TextField fullWidth margin="dense" label="연결 표시 이름" value={formLabel} onChange={(e) => setFormLabel(e.target.value)} />
          <TextField fullWidth margin="dense" label="호스트" value={formHost} onChange={(e) => setFormHost(e.target.value)} />
          <TextField fullWidth margin="dense" label="포트" value={formPort} onChange={(e) => setFormPort(e.target.value)} />
          <TextField fullWidth margin="dense" label="데이터베이스" value={formDb} onChange={(e) => setFormDb(e.target.value)} />
          <TextField fullWidth margin="dense" label="사용자" value={formUser} onChange={(e) => setFormUser(e.target.value)} />
          <TextField
            fullWidth
            margin="dense"
            label="비밀번호"
            type="password"
            value={formPass}
            onChange={(e) => setFormPass(e.target.value)}
            autoComplete="new-password"
          />
          <FormControlLabel control={<Checkbox checked={formSsl} onChange={(e) => setFormSsl(e.target.checked)} />} label="useSSL" />
          <FormControlLabel
            control={<Checkbox checked={formApk} onChange={(e) => setFormApk(e.target.checked)} />}
            label="allowPublicKeyRetrieval"
          />
        </DialogContent>
        <DialogActions>
          <Button onClick={() => setDialogOpen(false)}>취소</Button>
          <Button variant="contained" onClick={() => void submitConnect()}>
            연결
          </Button>
        </DialogActions>
      </Dialog>
    </Box>
  );
};

export default DashboardDbExplorerTab;
