const { useState, useEffect, useRef, useCallback, useMemo } = React;

// 字幕機文字寬度計算 (中文=1, 英文/ASCII=0.5)
function calculateTextWidth(text) {
  let width = 0;
  for (const ch of text) {
    width += ch.charCodeAt(0) > 127 ? 1 : 0.5;
  }
  return width;
}

// 取得本地時間的 YYYY-MM-DDTHH:mm 格式
function getLocalISO(date, type) {
  const d = new Date(date);
  const year = d.getFullYear();
  const month = String(d.getMonth() + 1).padStart(2, '0');
  const day = String(d.getDate()).padStart(2, '0');
  return type === 'start' ? `${year}-${month}-${day}T00:00` : `${year}-${month}-${day}T23:59`;
}

// 根據最大字數截斷文字
function truncateText(text, maxChars) {
  let width = 0;
  let result = '';
  for (const ch of text) {
    const w = ch.charCodeAt(0) > 127 ? 1 : 0.5;
    if (width + w > maxChars) break;
    width += w;
    result += ch;
  }
  return result;
}

// =============================================
// Reusable LogTable with Pagination, Sort, Filter
// =============================================
function LogTable({ logs: liveLogs, columns, emptyText, pageSize = 20, socket, logType, liveTotal }) {
  const [currentPage, setCurrentPage] = useState(1);
  const [currentPageSize, setCurrentPageSize] = useState(pageSize);
  const [sortKey, setSortKey] = useState(null);
  const [sortDir, setSortDir] = useState('asc');
  const [filter, setFilter] = useState('');

  // History / Search mode
  const [historyMode, setHistoryMode] = useState(false);
  const [historyLogs, setHistoryLogs] = useState([]);
  const [startTime, setStartTime] = useState(getLocalISO(new Date(), 'start'));
  const [endTime, setEndTime] = useState(getLocalISO(new Date(), 'end'));
  const [dbTotal, setDbTotal] = useState(0); 
  const [isFetching, setIsFetching] = useState(false);

  const displayLogs = historyMode ? historyLogs : liveLogs;

  // Reset page when logs change significantly
  useEffect(() => {
    setCurrentPage(1);
  }, [displayLogs.length > 0 ? 'has' : 'empty']);

  const handleSearch = (overrideStart, overrideEnd) => {
    if (!socket || !logType) return;
    const s = overrideStart !== undefined ? overrideStart : startTime;
    const e = overrideEnd !== undefined ? overrideEnd : endTime;
    
    setIsFetching(true);
    socket.emit('getLogsPaged', { 
      type: logType, 
      page: 1, 
      pageSize: 2000, 
      startTime: s ? new Date(s).toISOString() : null, 
      endTime: e ? new Date(e).toISOString() : null
    }, (res) => {
      setIsFetching(false);
      setHistoryLogs((res.logs || []).map(l => l.data || l));
      setDbTotal(res.overallTotal || 0);
      setHistoryMode(true);
      setCurrentPage(1);
    });
  };

  const handleTodayShortcut = () => {
    const s = getLocalISO(new Date(), 'start');
    const e = getLocalISO(new Date(), 'end');
    setStartTime(s);
    setEndTime(e);
    handleSearch(s, e);
  };

  const handleAllShortcut = () => {
    setStartTime('');
    setEndTime('');
    handleSearch('', '');
  };

  const handleClear = () => {
    setStartTime('');
    setEndTime('');
    setHistoryLogs([]);
    setHistoryMode(false);
    setCurrentPage(1);
    setFilter('');
  };

  const handleSort = (key) => {
    if (sortKey === key) {
      setSortDir(prev => prev === 'asc' ? 'desc' : 'asc');
    } else {
      setSortKey(key);
      setSortDir('asc');
    }
    setCurrentPage(1);
  };

  // Filter
  const filtered = useMemo(() => {
    if (!filter.trim()) return displayLogs;
    const q = filter.toLowerCase();
    return displayLogs.filter(log =>
      columns.some(col => {
        const val = col.accessor(log);
        return val && String(val).toLowerCase().includes(q);
      })
    );
  }, [displayLogs, filter, columns]);

  // Sort
  const sorted = useMemo(() => {
    if (!sortKey) return filtered;
    const col = columns.find(c => c.key === sortKey);
    if (!col) return filtered;
    return [...filtered].sort((a, b) => {
      const va = col.accessor(a) || '';
      const vb = col.accessor(b) || '';
      const cmp = String(va).localeCompare(String(vb), undefined, { numeric: true });
      return sortDir === 'asc' ? cmp : -cmp;
    });
  }, [filtered, sortKey, sortDir, columns]);

  // Pagination
  const totalPages = Math.max(1, Math.ceil(sorted.length / currentPageSize));
  const safeCurrentPage = Math.min(currentPage, totalPages);
  const startIdx = (safeCurrentPage - 1) * currentPageSize;
  const paged = sorted.slice(startIdx, startIdx + currentPageSize);

  const goPage = (p) => {
    setCurrentPage(Math.max(1, Math.min(p, totalPages)));
  };

  return (
    <div>
      {/* Toolbar: Filter + Info */}
      <div className="log-toolbar" style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
        <input
          className="filter-input"
          type="text"
          placeholder="🔍 關鍵字搜尋..."
          value={filter}
          onChange={(e) => { setFilter(e.target.value); setCurrentPage(1); }}
          style={{ width: '150px' }}
        />
        
        {socket && logType && (
          <div style={{ display: 'flex', gap: '6px', alignItems: 'center', marginLeft: '10px' }}>
            <span style={{color: 'var(--text-muted)', fontSize: '0.9rem'}}>起:</span>
            <input type="datetime-local" className="filter-input" value={startTime} onChange={e => setStartTime(e.target.value)} />
            <span style={{color: 'var(--text-muted)', fontSize: '0.9rem'}}>迄:</span>
            <input type="datetime-local" className="filter-input" value={endTime} onChange={e => setEndTime(e.target.value)} />
            <button className="btn btn-primary" style={{ padding: '6px 12px', fontSize: '0.9rem', whiteSpace: 'nowrap' }} title="當天查詢" onClick={handleTodayShortcut}>今天</button>
            <button className="btn" style={{ padding: '6px 12px', fontSize: '0.9rem', whiteSpace: 'nowrap' }} title="全部歷史查詢" onClick={handleAllShortcut}>全部</button>
            <button className="btn btn-primary" style={{ padding: '6px 12px', minWidth: '40px', fontSize: '0.9rem', whiteSpace: 'nowrap' }} disabled={isFetching} onClick={() => handleSearch()}>
              {isFetching ? '⏳...' : '🔍'}
            </button>
            {historyMode && (
              <button className="btn" style={{ padding: '6px 12px', fontSize: '0.9rem', whiteSpace: 'nowrap' }} onClick={handleClear}>重置</button>
            )}
          </div>
        )}

        <span className="log-info" style={{ marginLeft: 'auto', fontWeight: '500' }}>
          {historyMode ? (
            <>
              顯示 {sorted.length} {filter ? `(篩選自 ${historyLogs.length})` : ''} / 總筆數 {dbTotal}
            </>
          ) : (
            <>
              最新 {sorted.length} 筆 {filter ? `(篩選自 ${liveLogs.length})` : ''} {liveTotal ? `/ 總筆數 ${liveTotal}` : ''}
            </>
          )}
        </span>
      </div>

      <table>
        <thead>
          <tr>
            {columns.map(col => (
              <th
                key={col.key}
                className="sortable"
                onClick={() => handleSort(col.key)}
                title="點擊排序"
              >
                {col.label}
                {sortKey === col.key && (
                  <span className="sort-arrow">{sortDir === 'asc' ? ' ▲' : ' ▼'}</span>
                )}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {paged.length === 0 ? (
            <tr>
              <td colSpan={columns.length} style={{ textAlign: 'center', color: 'var(--text-muted)' }}>
                {emptyText || '暫無資料'}
              </td>
            </tr>
          ) : (
            paged.map((log, index) => (
              <tr key={startIdx + index}>
                {columns.map(col => (
                  <td key={col.key}>{col.accessor(log) ?? '-'}</td>
                ))}
              </tr>
            ))
          )}
        </tbody>
      </table>

      {/* Pagination */}
      {sorted.length > 0 && (
        <div className="pagination">
          <div className="page-size-selector" style={{ marginRight: 'auto', display: 'flex', alignItems: 'center', gap: '8px' }}>
            <span style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>每頁顯示:</span>
            <select 
              value={currentPageSize} 
              onChange={(e) => { 
                setCurrentPageSize(Number(e.target.value)); 
                setCurrentPage(1); 
              }}
              style={{ padding: '4px 8px', backgroundColor: 'var(--btn-bg)', border: '1px solid var(--btn-border)', borderRadius: '4px', color: 'var(--text-color)', fontSize: '0.85rem' }}
            >
              {[20, 50, 100, 200].map(size => (
                <option key={size} value={size}>{size}</option>
              ))}
            </select>
          </div>

          <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
            <button className="page-btn" disabled={safeCurrentPage <= 1} onClick={() => goPage(1)}>«</button>
            <button className="page-btn" disabled={safeCurrentPage <= 1} onClick={() => goPage(safeCurrentPage - 1)}>‹</button>
            <span className="page-info">第 {safeCurrentPage} / {totalPages} 頁</span>
            <button className="page-btn" disabled={safeCurrentPage >= totalPages} onClick={() => goPage(safeCurrentPage + 1)}>›</button>
            <button className="page-btn" disabled={safeCurrentPage >= totalPages} onClick={() => goPage(totalPages)}>»</button>
          </div>
          
          <div style={{ marginLeft: 'auto', width: '100px' }}></div> 
        </div>
      )}
    </div>
  );
}

// =============================================
// Main App
// =============================================
function LoginScreen({ onLogin }) {
  const [userId, setUserId] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!userId || !password) return setError('請輸入帳號和密碼');
    setLoading(true);
    fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId, password })
    })
      .then(res => res.json())
      .then(data => {
        setLoading(false);
        if (data.success) {
          onLogin(data.token, data.username || 'Admin');
        } else {
          setError(data.error || '登入失敗');
        }
      })
      .catch(err => {
        setLoading(false);
        setError('網路錯誤');
      });
  };

  return (
    <div className="login-container">
      <div className="login-box">
        <h2>登入管理</h2>
        {error && <div className="error-message" style={{ color: 'red', marginBottom: '10px' }}>{error}</div>}
        <form onSubmit={handleSubmit}>
          <div className="form-group">
            <label>帳號 (User ID)</label>
            <input type="text" value={userId} onChange={e => setUserId(e.target.value)} required />
          </div>
          <div className="form-group">
            <label>密碼 (Password)</label>
            <input type="password" value={password} onChange={e => setPassword(e.target.value)} required />
          </div>
          <button type="submit" className="btn btn-primary" style={{ width: '100%', marginTop: '15px' }} disabled={loading}>
            {loading ? '登入中...' : '登入'}
          </button>
        </form>
      </div>
    </div>
  );
}

// =============================================
// Main App
// =============================================
const DIO_COLORS = {
  di: '#2980b9', // Belize Hole Blue
  do: '#16a085'  // Green Sea
};

function App() {
  // Add global style for DIO animations
  useEffect(() => {
    const style = document.createElement('style');
    style.innerHTML = `
      @keyframes dio-badge-flash-di {
        0% { transform: scale(1); background-color: transparent; box-shadow: none; }
        50% { transform: scale(1.4); background-color: ${DIO_COLORS.di}; color: #fff; box-shadow: 0 0 10px ${DIO_COLORS.di}; }
        100% { transform: scale(1); background-color: transparent; box-shadow: none; }
      }
      @keyframes dio-badge-flash-do {
        0% { transform: scale(1); background-color: transparent; box-shadow: none; }
        50% { transform: scale(1.4); background-color: ${DIO_COLORS.do}; color: #fff; box-shadow: 0 0 10px ${DIO_COLORS.do}; }
        100% { transform: scale(1); background-color: transparent; box-shadow: none; }
      }
      .dio-badge-animate-di {
        animation: dio-badge-flash-di 0.6s ease-in-out 2;
      }
      .dio-badge-animate-do {
        animation: dio-badge-flash-do 0.6s ease-in-out 2;
      }
    `;
    document.head.appendChild(style);
    return () => document.head.removeChild(style);
  }, []);

  // Auth and UI state
  const [loadingConfig, setLoadingConfig] = useState(true);
  const [loginEnabled, setLoginEnabled] = useState(false);
  const [authToken, setAuthToken] = useState(localStorage.getItem('ezplatehub_token') || null);
  const [loggedIn, setLoggedIn] = useState(false);
  const [ready, setReady] = useState(false);

  const [activeTab, setActiveTab] = useState('access');
  const [accessDevices, setAccessDevices] = useState([]);
  const [textDisplayDevices, setTextDisplayDevices] = useState([]);
  const [etagDevices, setEtagDevices] = useState([]);
  const [etagLogs, setEtagLogs] = useState([]);
  const [etagTotal, setEtagTotal] = useState(0);
  const [accessLogs, setAccessLogs] = useState([]);
  const [accessTotal, setAccessTotal] = useState(0);
  const [socket, setSocket] = useState(null);
  const [textDisplayLogs, setTextDisplayLogs] = useState([]);
  const [textDisplayTotal, setTextDisplayTotal] = useState(0);
  const [mqttLogs, setMqttLogs] = useState([]);
  const [mqttTotal, setMqttTotal] = useState(0);
  const [commandStatus, setCommandStatus] = useState(null);
  const [textDisplayStatus, setTextDisplayStatus] = useState(null);
  const [mqttSettings, setMqttSettings] = useState({ protocol: 'mqtt', host: '', username: '', password: '' });
  const [mqttStatus, setMqttStatus] = useState({ status: 'disconnected', config: null, lastError: '' });
  const [deviceStatus, setDeviceStatus] = useState({});
  const [doorStates, setDoorStates] = useState({}); // { deviceId: { state, pollData } }
  const [visitorPlateConfig, setVisitorPlateConfig] = useState(null);
  const [visitorPlateData, setVisitorPlateData] = useState({ plates: [], events: [] });

  useEffect(() => {
    fetch('/api/config')
      .then(res => res.json())
      .then(data => {
        setLoginEnabled(data.loginEnabled);
        if (!data.loginEnabled || authToken) {
          setLoggedIn(true);
        }
        setLoadingConfig(false);
      })
      .catch(err => {
        console.error('Failed to fetch config', err);
        setLoadingConfig(false);
      });
  }, [authToken]);

  useEffect(() => {
    if (!loggedIn && loginEnabled) return; // Block socket connect if user not logged in but required

    const newSocket = io({ auth: { token: authToken } });
    setSocket(newSocket);

    newSocket.on('connect_error', (err) => {
      console.error('Socket connect error:', err.message);
      if (err.message === 'Authentication error') {
        localStorage.removeItem('ezplatehub_token');
        setAuthToken(null);
        setLoggedIn(false);
        setReady(false);
      }
    });

    newSocket.on('connect', () => {
      console.log('Connected to backend WebSocket');
    });

    newSocket.on('init', (data) => {
      setAccessDevices(data.accessDevices || []);
      setTextDisplayDevices(data.textDisplayDevices || []);
      setEtagDevices(data.etagDevices || []);
      setAccessLogs((data.accessLogs || []).map(l => l.data || l));
      setTextDisplayLogs((data.textDisplayLogs || []).map(l => l.data || l));
      setMqttLogs((data.mqttLogs || []).map(l => l.data || l));
      setEtagLogs((data.etagLogs || []).map(l => l.data || l));
      setAccessTotal(data.accessTotal || 0);
      setTextDisplayTotal(data.textDisplayTotal || 0);
      setMqttTotal(data.mqttTotal || 0);
      setEtagTotal(data.etagTotal || 0);
      if (data.mqttSettings) setMqttSettings(data.mqttSettings);
      if (data.mqttStatus) setMqttStatus(data.mqttStatus);
      if (data.deviceStatus) setDeviceStatus(data.deviceStatus);
      if (data.doorStates) setDoorStates(data.doorStates);
      if (data.visitorPlateConfig) setVisitorPlateConfig(data.visitorPlateConfig);
      if (data.visitorPlateData) setVisitorPlateData(data.visitorPlateData);
      setReady(true);
    });

    newSocket.on('visitorPlateUpdated', (data) => {
      if (data) setVisitorPlateData(data);
    });

    newSocket.on('devicesUpdated', ({ type, devices }) => {
      if (type === 'access') setAccessDevices(devices);
      else if (type === 'textDisplay') setTextDisplayDevices(devices);
      else if (type === 'etag') setEtagDevices(devices);
    });

    newSocket.on('etagCardEvent', (event) => {
      setEtagLogs((prev) => [event, ...prev].slice(0, 500));
      setEtagTotal(prev => prev + 1);
    });

    newSocket.on('pushEvent', (event) => {
      setAccessLogs((prev) => {
        const updated = [event, ...prev];
        return updated.slice(0, 500);
      });
      setAccessTotal(prev => prev + 1);
    });

    newSocket.on('textDisplayLog', (event) => {
      setTextDisplayLogs((prev) => {
        const updated = [event, ...prev];
        return updated.slice(0, 500);
      });
      setTextDisplayTotal(prev => prev + 1);
    });

    newSocket.on('mqttLog', (event) => {
      setMqttLogs((prev) => {
        const updated = [event, ...prev];
        return updated.slice(0, 500);
      });
      setMqttTotal(prev => prev + 1);
    });

    newSocket.on('mqttStatus', (status) => {
      setMqttStatus(status);
    });

    newSocket.on('deviceStatusUpdate', (statusMap) => {
      setDeviceStatus(statusMap);
    });

    newSocket.on('doorStatusUpdate', ({ deviceId, state, pollData }) => {
      setDoorStates(prev => ({ ...prev, [deviceId]: { state, pollData } }));
    });

    newSocket.on('commandStatus', (status) => {
      setCommandStatus(status);
      setTimeout(() => setCommandStatus(null), 3000);
    });

    newSocket.on('textDisplayStatus', (status) => {
      setTextDisplayStatus(status);
      setTimeout(() => setTextDisplayStatus(null), 3000);
    });

    return () => newSocket.close();
  }, [loggedIn, loginEnabled, authToken]);

  const handleLogin = (token) => {
    localStorage.setItem('ezplatehub_token', token);
    setAuthToken(token);
  };

  const handleLogout = () => {
    localStorage.removeItem('ezplatehub_token');
    setAuthToken(null);
    setLoggedIn(false);
    setReady(false);
  };

  if (loadingConfig) {
    return <div style={{ color: 'white', textAlign: 'center', marginTop: '50px' }}>讀取設定中...</div>;
  }

  if (loginEnabled && !loggedIn) {
    return <LoginScreen onLogin={handleLogin} />;
  }

  if (!ready) {
    return <div style={{ color: 'white', textAlign: 'center', marginTop: '50px' }}>連線建立中...</div>;
  }

  return (
    <div className="app-container">
      <h1 className="app-title">EZCON車牌辨識中介控制程式</h1>
      {/* Tab Bar */}
      <div className="tab-bar">
        <button
          className={`tab-btn ${activeTab === 'access' ? 'active' : ''}`}
          onClick={() => setActiveTab('access')}
        >
          🔐 門禁控制
        </button>
        <button
          className={`tab-btn ${activeTab === 'etag' ? 'active' : ''}`}
          onClick={() => setActiveTab('etag')}
        >
          📻 ETag
        </button>
        <button
          className={`tab-btn ${activeTab === 'textDisplay' ? 'active' : ''}`}
          onClick={() => setActiveTab('textDisplay')}
        >
          📺 字幕機控制
        </button>
        <button
          className={`tab-btn ${activeTab === 'mqtt' ? 'active' : ''}`}
          onClick={() => setActiveTab('mqtt')}
        >
          📡 MQTT
        </button>
        <button
          className={`tab-btn ${activeTab === 'visitorPlate' ? 'active' : ''}`}
          onClick={() => setActiveTab('visitorPlate')}
        >
          🎫 訪客車牌預約
        </button>
        <button
          className={`tab-btn ${activeTab === 'settings' ? 'active' : ''}`}
          onClick={() => setActiveTab('settings')}
        >
          ⚙️ 設定
        </button>
        {loginEnabled && (
          <button className="btn-icon" style={{ marginLeft: 'auto', fontSize: '1.2rem', padding: '0 10px' }} title="登出系統" onClick={handleLogout}>
            🚪
          </button>
        )}
      </div>

      {/* Tab Content — all tabs stay mounted, only active tab is visible */}
      <div className="tab-content">
        <div style={{ display: activeTab === 'access' ? 'block' : 'none' }}>
          <AccessTab
            socket={socket}
            devices={accessDevices}
            siblingDevices={etagDevices}
            logs={accessLogs}
            total={accessTotal}
            commandStatus={commandStatus}
            deviceStatus={deviceStatus}
            doorStates={doorStates}
          />
        </div>
        <div style={{ display: activeTab === 'textDisplay' ? 'block' : 'none' }}>
          <TextDisplayTab
            socket={socket}
            devices={textDisplayDevices}
            logs={textDisplayLogs}
            total={textDisplayTotal}
            status={textDisplayStatus}
            deviceStatus={deviceStatus}
          />
        </div>
        <div style={{ display: activeTab === 'etag' ? 'block' : 'none' }}>
          <EtagTab
            socket={socket}
            devices={etagDevices}
            siblingDevices={accessDevices}
            logs={etagLogs}
            total={etagTotal}
            deviceStatus={deviceStatus}
          />
        </div>
        <div style={{ display: activeTab === 'mqtt' ? 'block' : 'none' }}>
          <MqttTab
            socket={socket}
            settings={mqttSettings}
            setSettings={setMqttSettings}
            status={mqttStatus}
            logs={mqttLogs}
            total={mqttTotal}
            accessDevices={accessDevices}
            textDisplayDevices={textDisplayDevices}
          />
        </div>
        <div style={{ display: activeTab === 'visitorPlate' ? 'block' : 'none' }}>
          <VisitorPlateTab
            socket={socket}
            config={visitorPlateConfig}
            setConfig={setVisitorPlateConfig}
            data={visitorPlateData}
          />
        </div>
        <div style={{ display: activeTab === 'settings' ? 'block' : 'none' }}>
          <SettingsTab authToken={authToken} onLogout={handleLogout} />
        </div>
      </div>
    </div>
  );
}

// =============================================
// Add / Edit Device Modal
// =============================================
function DeviceModal({ type, device, devices, siblingDevices = [], onClose, socket }) {
  const isEdit = !!device;
  const typeLabel = type === 'access' ? '門禁裝置' : (type === 'etag' ? 'ETag 感應器' : '字幕機');
  const defaultPort = type === 'access' ? 1621 : (type === 'etag' ? 1621 : 23);
  const defaultTimeout = type === 'etag' ? 90 : 5000;

  // access + etag share the same stationId namespace (card/trigger payload
  // uses station as identifier). siblingDevices carries the other group.
  const reservedIds = new Set([
    ...(devices || []).map(d => Number(d.stationId)),
    ...(siblingDevices || []).map(d => Number(d.stationId)),
  ]);
  let nextStationId = 1;
  if (!isEdit) {
    while (reservedIds.has(nextStationId)) nextStationId++;
  }

  const [form, setForm] = useState({
    name: device?.name || '',
    stationId: device?.stationId || nextStationId,
    host: device?.host || '',
    port: device?.port || defaultPort,
    nodeId: device?.nodeId || 1,
    timeoutMs: device?.extra?.timeoutMs || defaultTimeout,
    pushServerPort: device?.extra?.pushServerPort || 8031,
    isPolling: device?.extra?.isPolling !== undefined ? device.extra.isPolling : (type === 'etag'),
    pollingInterval: device?.extra?.pollingInterval || 1000,
    maxChars: device?.extra?.maxChars || 4,
    hwType: device?.extra?.hwType || 'lmc',
    displayAddr: device?.extra?.displayAddr ?? 0,
    category: device?.extra?.category || '門禁',
    doPulseSeconds: device?.extra?.doPulseSeconds || [8, 8, 8, 8],
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setForm(prev => {
      let finalValue = value;
      if (type === 'checkbox') finalValue = checked;
      else if (name !== 'host' && name !== 'name' && name !== 'hwType' && name !== 'category') finalValue = Number(value);

      const updated = {
        ...prev,
        [name]: finalValue
      };
      // When switching hwType, update default port
      if (name === 'hwType' && type === 'textDisplay' && !isEdit) {
        updated.port = value === 'avt' ? 7 : 23;
      }
      if (name === 'category' && type === 'access' && !isEdit) {
        updated.port = value === 'DIO' ? 502 : 1621;
        if (value === 'DIO') {
          updated.isPolling = true;
          updated.pollingInterval = 1000;
        }
      }
      return updated;
    });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const extra = { timeoutMs: form.timeoutMs };
    if (type === 'access') {
      extra.pushServerPort = form.pushServerPort;
      extra.isPolling = form.isPolling;
      extra.pollingInterval = form.pollingInterval;
      extra.category = form.category;
      if (form.category === 'DIO') {
        extra.doPulseSeconds = form.doPulseSeconds;
      }
    }
    if (type === 'textDisplay') {
      extra.hwType = form.hwType;
      if (form.hwType === 'avt') {
        extra.displayAddr = form.displayAddr;
      } else {
        extra.maxChars = form.maxChars;
      }
    }
    if (type === 'etag') {
      extra.isPolling = form.isPolling;
      extra.pollingInterval = form.pollingInterval;
    }

    if (isEdit) {
      socket.emit('updateDevice', {
        id: device.id,
        fields: {
          name: form.name,
          stationId: form.stationId,
          host: form.host,
          port: form.port,
          nodeId: form.nodeId,
          extra,
        }
      }, (res) => {
        if (res.success) onClose();
        else alert('更新失敗: ' + res.error);
      });
    } else {
      socket.emit('addDevice', {
        type,
        stationId: form.stationId,
        name: form.name,
        host: form.host,
        port: form.port,
        nodeId: form.nodeId,
        extra,
      }, (res) => {
        if (res.success) onClose();
        else alert('新增失敗: ' + res.error);
      });
    }
  };

  const isAvt = form.hwType === 'avt';

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <h3>{isEdit ? `編輯 ${typeLabel}` : `新增 ${typeLabel}`}</h3>
        <form onSubmit={handleSubmit}>
          {type === 'access' && (
            <div className="form-group">
              <label>類別 (Category)</label>
              <select name="category" value={form.category} onChange={handleChange}
                style={{ width: '100%', padding: '8px 12px', backgroundColor: 'var(--bg-color)',
                  border: '1px solid var(--border-color)', borderRadius: '6px',
                  color: 'var(--text-color)', fontSize: '0.95rem' }}>
                <option value="門禁">門禁</option>
                <option value="DIO">DIO 裝置</option>
              </select>
            </div>
          )}
          {type === 'textDisplay' && (
            <div className="form-group">
              <label>硬體類型</label>
              <select name="hwType" value={form.hwType} onChange={handleChange}
                style={{ width: '100%', padding: '8px 12px', backgroundColor: 'var(--bg-color)',
                  border: '1px solid var(--border-color)', borderRadius: '6px',
                  color: 'var(--text-color)', fontSize: '0.95rem' }}>
                <option value="lmc">LM-C 字幕機 (TCP)</option>
                <option value="avt">AVT 字幕機 (UDP)</option>
              </select>
            </div>
          )}
          <div className="form-group">
            <label>裝置名稱</label>
            <input type="text" name="name" value={form.name} onChange={handleChange} placeholder={`${typeLabel} 1`} required />
          </div>
          <div className="form-group">
            <label>Station ID (不可重複)</label>
            <input type="number" name="stationId" value={form.stationId} onChange={handleChange} min="1" required />
          </div>
          <div className="form-row">
            <div className="form-group">
              <label>主機 IP</label>
              <input type="text" name="host" value={form.host} onChange={handleChange} required />
            </div>
            <div className="form-group">
              <label>Port</label>
              <input type="number" name="port" value={form.port} onChange={handleChange} required />
            </div>
            {type !== 'etag' && (
              <div className="form-group">
                <label>站號 (Node ID)</label>
                <input type="number" name="nodeId" value={form.nodeId} onChange={handleChange} min="0" required />
              </div>
            )}
          </div>
          {type === 'textDisplay' && isAvt && (
            <div className="form-group">
              <label>顯示地址 (Display Address)</label>
              <select name="displayAddr" value={form.displayAddr} onChange={handleChange}
                style={{ width: '100%', padding: '8px 12px', backgroundColor: 'var(--bg-color)',
                  border: '1px solid var(--border-color)', borderRadius: '6px',
                  color: 'var(--text-color)', fontSize: '0.95rem' }}>
                <option value={0}>0 (上半區)</option>
                <option value={1}>1 (下半區)</option>
              </select>
            </div>
          )}
          <div className="form-group">
            <label>指令超時 (ms)</label>
            <input type="number" name="timeoutMs" value={form.timeoutMs} onChange={handleChange}
              min={type === 'etag' ? 10 : 1000}
              step={type === 'etag' ? 10 : 1000}
              required />
          </div>
          {type === 'access' && (
            <>
              <div className="form-group">
                <label>推送伺服器 Port</label>
                <input type="number" name="pushServerPort" value={form.pushServerPort} onChange={handleChange} required />
              </div>
              <div className="form-group" style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '15px' }}>
                <input type="checkbox" name="isPolling" id="isPollingCheck" checked={form.isPolling} onChange={handleChange} style={{ width: '18px', height: '18px' }} />
                <label htmlFor="isPollingCheck" style={{ marginBottom: 0 }}>啟用門位查詢 (Polling)</label>
              </div>
              {form.isPolling && (
                <div className="form-group" style={{ marginTop: '10px' }}>
                  <label>輪詢間隔 (毫秒)</label>
                  <input type="number" name="pollingInterval" value={form.pollingInterval} onChange={handleChange} min="50" required />
                </div>
              )}
              {form.category === 'DIO' && (
                <div style={{ marginTop: '15px', padding: '10px', backgroundColor: 'var(--bg-lighter)', borderRadius: '6px', border: '1px solid var(--border-color)' }}>
                  <label style={{ fontSize: '0.85rem', fontWeight: '600', marginBottom: '8px', display: 'block' }}>DO Pulse 秒數設定 (1~60s)</label>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: '10px' }}>
                    {[0, 1, 2, 3].map(i => (
                      <div key={i} className="form-group" style={{ marginBottom: 0 }}>
                        <label style={{ fontSize: '0.75rem', marginBottom: '4px' }}>DO {i} (s)</label>
                        <input 
                          type="number" 
                          value={form.doPulseSeconds[i]} 
                          min="0.1" 
                          max="60"
                          step="0.1"
                          onChange={(e) => {
                            const val = Number(e.target.value);
                            setForm(prev => {
                              const newPulse = [...prev.doPulseSeconds];
                              newPulse[i] = val;
                              return { ...prev, doPulseSeconds: newPulse };
                            });
                          }}
                          required 
                        />
                      </div>
                    ))}
                  </div>
                </div>
              )}
            </>
          )}
          {type === 'etag' && (
            <>
              <div className="form-group" style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '15px' }}>
                <input type="checkbox" name="isPolling" id="etagPollingCheck" checked={form.isPolling} onChange={handleChange} style={{ width: '18px', height: '18px' }} />
                <label htmlFor="etagPollingCheck" style={{ marginBottom: 0 }}>啟用輪詢</label>
              </div>
              {form.isPolling && (
                <div className="form-group" style={{ marginTop: '10px' }}>
                  <label>輪詢間隔 (毫秒)</label>
                  <input type="number" name="pollingInterval" value={form.pollingInterval} onChange={handleChange} min="50" required />
                </div>
              )}
            </>
          )}
          {type === 'textDisplay' && !isAvt && (
            <div className="form-group">
              <label>最大字數 (中文=1, 英數=0.5)</label>
              <input type="number" name="maxChars" value={form.maxChars} onChange={handleChange} min="1" required />
            </div>
          )}
          <div className="modal-actions">
            <button type="button" className="btn" onClick={onClose}>取消</button>
            <button type="submit" className="btn btn-primary">{ isEdit ? '更新' : '新增'}</button>
          </div>
        </form>
      </div>
    </div>
  );
}

// =============================================
// Custom Confirm Modal
// =============================================
function ConfirmModal({ message, onConfirm, onClose }) {
  return (
    <div className="modal-overlay" onClick={onClose} style={{ zIndex: 2000 }}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '400px' }}>
        <h3 style={{ color: 'var(--danger-color)', marginTop: 0 }}>⚠️ 警告</h3>
        <p style={{ margin: '20px 0', fontSize: '1.05rem' }}>{message}</p>
        <div className="modal-actions" style={{ marginTop: '20px' }}>
          <button type="button" className="btn" onClick={onClose}>取消</button>
          <button type="button" className="btn btn-primary" style={{ backgroundColor: 'var(--danger-color)', borderColor: 'var(--danger-color)' }} onClick={onConfirm}>確定刪除</button>
        </div>
      </div>
    </div>
  );
}

// =============================================
// 訪客車牌預約 Tab
// =============================================
function vpFmtLocal(d) {
  const p = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}/${p(d.getMonth() + 1)}/${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
}
function vpFmtPeriod(s) {
  if (!s) return '';
  // 顯示用: 2026-06-29T14:30:00+08:00 → 2026/06/29 14:30
  const m = String(s).match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/);
  return m ? `${m[1]}/${m[2]}/${m[3]} ${m[4]}:${m[5]}` : s;
}

function VisitorPlateTab({ socket, config, setConfig, data }) {
  const plates = (data && data.plates) || [];
  const events = (data && data.events) || [];

  // --- 模擬測試 ---
  const [simPlate, setSimPlate] = useState('');
  const [simHours, setSimHours] = useState(4);
  const [simBusy, setSimBusy] = useState(false);
  const [simResult, setSimResult] = useState(null);

  // --- 設定 ---
  const [cfg, setCfg] = useState(config || {});
  const [pwInput, setPwInput] = useState('');
  const [tokenInput, setTokenInput] = useState('');
  const [savingCfg, setSavingCfg] = useState(false);
  const [cfgFeedback, setCfgFeedback] = useState('');
  const [testResult, setTestResult] = useState('');
  const [pathWarn, setPathWarn] = useState(null);   // 待確認的新路徑
  const [rowDelete, setRowDelete] = useState(null);  // 待確認刪除的車牌 row

  useEffect(() => { if (config) setCfg(config); }, [config]);

  if (!socket) return <div className="tab-panel"><p style={{ padding: 20 }}>連線建立中...</p></div>;

  const liveCfg = config || {};
  const origin = (typeof window !== 'undefined' && window.location) ? window.location.origin : '';
  const inboundUrl = `${origin}${liveCfg.apiPath || '/api/event'}?事件名稱=${encodeURIComponent(liveCfg.visitorEventName || '訪客車牌')}&車牌=ABC-1234&開始時間=${encodeURIComponent('2026/06/29;14:30')}&結束時間=${encodeURIComponent('2026/06/29;18:00')}`;

  const previewStart = vpFmtLocal(new Date());
  const previewEnd = vpFmtLocal(new Date(Date.now() + (Number(simHours) || 4) * 3600 * 1000));

  // --- handlers ---
  const handleSimulate = () => {
    const lic = simPlate.trim();
    if (!lic) { setSimResult({ success: false, error: '請輸入車牌' }); return; }
    setSimBusy(true); setSimResult(null);
    socket.emit('visitorPlate:simulate', { license: lic, hours: Number(simHours) || 4 }, (res) => {
      setSimBusy(false); setSimResult(res || { success: false, error: '無回應' });
    });
  };

  const handleSimDelete = () => {
    const lic = simPlate.trim();
    if (!lic) { setSimResult({ success: false, error: '請輸入車牌' }); return; }
    setSimBusy(true);
    socket.emit('visitorPlate:delete', { license: lic }, (res) => {
      setSimBusy(false);
      setSimResult(res && res.success
        ? { success: true, result: { ...(res.result || {}), action: 'delete' } }
        : (res || { success: false, error: '無回應' }));
    });
  };

  const handleTest = () => {
    setTestResult('測試中...');
    socket.emit('visitorPlate:test', (res) => {
      if (res && res.success) setTestResult(`✅ 連線成功，目前訪客車輛 ${res.result.count} 筆`);
      else setTestResult('❌ ' + ((res && res.error) || '連線失敗'));
    });
  };

  const handleRefresh = () => {
    setCfgFeedback('同步中...');
    socket.emit('visitorPlate:refresh', (res) => {
      if (res && res.success) setCfgFeedback(`✅ 已從伺服器同步 ${res.result.count} 筆訪客車輛`);
      else setCfgFeedback('❌ 同步失敗: ' + ((res && res.error) || ''));
    });
  };

  const saveConfig = (extra = {}) => {
    setSavingCfg(true); setCfgFeedback('');
    const payload = {
      enabled: !!cfg.enabled,
      baseUrl: cfg.baseUrl,
      username: cfg.username,
      tzOffset: cfg.tzOffset,
      role: cfg.role,
      visitorEventName: cfg.visitorEventName,
      ...extra,
    };
    if (pwInput) payload.password = pwInput;
    if (tokenInput) payload.manualToken = tokenInput;
    socket.emit('visitorPlate:setConfig', payload, (res) => {
      setSavingCfg(false);
      if (res && res.success) {
        setConfig(res.config); setCfg(res.config);
        setPwInput(''); setTokenInput('');
        setCfgFeedback('✅ 設定已儲存');
      } else {
        setCfgFeedback('❌ ' + ((res && res.error) || '儲存失敗'));
      }
    });
  };

  const clearToken = () => {
    socket.emit('visitorPlate:setConfig', { manualToken: '' }, (res) => {
      if (res && res.success) { setConfig(res.config); setCfg(res.config); setTokenInput(''); setCfgFeedback('✅ 已清除手動 Token'); }
      else setCfgFeedback('❌ ' + ((res && res.error) || ''));
    });
  };

  const requestPathChange = () => {
    const newPath = (cfg.apiPath || '').trim();
    if (!newPath) { setCfgFeedback('❌ 路徑不可空白'); return; }
    if (newPath === liveCfg.apiPath) { setCfgFeedback('路徑未變更'); return; }
    setPathWarn(newPath);  // 開啟警告視窗
  };

  const confirmPathChange = () => {
    const newPath = pathWarn; setPathWarn(null);
    saveConfig({ apiPath: newPath });
  };

  const thStyle = { textAlign: 'left', padding: '8px 10px', borderBottom: '2px solid var(--border-color)', color: 'var(--text-muted)', fontSize: '0.85rem', whiteSpace: 'nowrap' };
  const tdStyle = { padding: '8px 10px', borderBottom: '1px solid var(--border-color)', fontSize: '0.9rem' };
  const codeStyle = { fontFamily: 'monospace', fontSize: '0.85rem', wordBreak: 'break-all' };

  const statusBadge = (s) => {
    const map = { ok: ['#1f7a1f', '✅ 成功'], error: ['var(--danger-color)', '❌ 失敗'], no_vehicle_id: ['var(--warning-color)', '⚠️ 無ID'], ignored: ['var(--text-muted)', '略過'], not_found: ['var(--text-muted)', '查無'] };
    const [c, t] = map[s] || ['var(--text-muted)', s || ''];
    return <span style={{ color: c, fontWeight: 600 }}>{t}</span>;
  };

  return (
    <div className="tab-panel">
      {/* ===================== 模擬測試 ===================== */}
      <div className="panel" style={{ borderLeft: '4px solid var(--primary-color)' }}>
        <h3 style={{ marginTop: 0 }}>🧪 模擬測試</h3>
        <p style={{ color: 'var(--text-muted)', marginTop: 0 }}>
          輸入車牌與時數，系統以「現在」為起始時間、加上時數為結束時間，送出一筆訪客車牌預約。
        </p>
        <div className="form-row" style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'flex-end' }}>
          <div className="form-group" style={{ flex: '1 1 180px' }}>
            <label>車牌</label>
            <input type="text" value={simPlate} placeholder="例如 AAA-0001"
              onChange={(e) => setSimPlate(e.target.value.toUpperCase())}
              onKeyDown={(e) => { if (e.key === 'Enter') handleSimulate(); }} />
          </div>
          <div className="form-group" style={{ flex: '0 0 120px' }}>
            <label>幾小時</label>
            <input type="number" min="1" step="1" value={simHours}
              onChange={(e) => setSimHours(e.target.value)} />
          </div>
          <div className="form-group" style={{ flex: '2 1 260px' }}>
            <label>預估時段 (依瀏覽器時間)</label>
            <div style={{ color: 'var(--text-muted)', fontSize: '0.9rem', padding: '6px 0' }}>
              {previewStart} → {previewEnd}
            </div>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 10, marginTop: 8, flexWrap: 'wrap' }}>
          <button className="btn btn-primary" onClick={handleSimulate} disabled={simBusy}>
            {simBusy ? '處理中...' : '🚗 送出預約 (新增/更新)'}
          </button>
          <button className="btn" style={{ backgroundColor: 'var(--danger-color)', borderColor: 'var(--danger-color)', color: '#fff' }}
            onClick={handleSimDelete} disabled={simBusy}>
            🗑️ 刪除此車牌預約
          </button>
        </div>
        {simResult && (
          <div style={{ marginTop: 12, padding: 12, borderRadius: 6, background: 'var(--bg-color)', border: '1px solid var(--border-color)' }}>
            {simResult.success === false && simResult.error ? (
              <span style={{ color: 'var(--danger-color)' }}>❌ {simResult.error}</span>
            ) : simResult.result ? (
              <div>
                <div>動作: <b>{simResult.result.action}</b> &nbsp; 狀態: {statusBadge(simResult.result.status)}</div>
                <div style={{ marginTop: 6 }}>最新 vehicle_id: <span style={codeStyle}>{simResult.result.vehicleId || '(伺服器未回傳，待修正)'}</span></div>
                {simResult.result.periodsCount !== undefined && <div style={{ marginTop: 4 }}>目前時段數: {simResult.result.periodsCount}</div>}
                {simResult.result.error && <div style={{ marginTop: 4, color: 'var(--danger-color)' }}>訊息: {simResult.result.error}</div>}
              </div>
            ) : (
              <span style={{ color: 'var(--danger-color)' }}>❌ {simResult.error || '未知結果'}</span>
            )}
          </div>
        )}
      </div>

      {/* ===================== 目前預約車牌 ===================== */}
      <div className="panel">
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <h3 style={{ margin: 0 }}>🅿️ 目前預約車牌 ({plates.length})</h3>
          <button className="btn" onClick={handleRefresh}>🔄 從伺服器同步</button>
        </div>
        <div className="log-container" style={{ overflowX: 'auto', marginTop: 10 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={thStyle}>車牌</th>
                <th style={thStyle}>最新 vehicle_id</th>
                <th style={thStyle}>時段數</th>
                <th style={thStyle}>事件次數</th>
                <th style={thStyle}>最後事件</th>
                <th style={thStyle}>最後時段</th>
                <th style={thStyle}>更新時間</th>
                <th style={thStyle}>操作</th>
              </tr>
            </thead>
            <tbody>
              {plates.length === 0 && (
                <tr><td style={tdStyle} colSpan={8}><span style={{ color: 'var(--text-muted)' }}>尚無資料</span></td></tr>
              )}
              {plates.map((p) => (
                <tr key={p.license}>
                  <td style={{ ...tdStyle, fontWeight: 600 }}>{p.license}</td>
                  <td style={{ ...tdStyle, ...codeStyle }}>{p.vehicleId || <span style={{ color: 'var(--warning-color)' }}>(待回傳)</span>}</td>
                  <td style={tdStyle}>{p.periodsCount}</td>
                  <td style={tdStyle}>{p.eventCount}</td>
                  <td style={tdStyle}>{p.lastEventName}</td>
                  <td style={tdStyle}>{vpFmtPeriod(p.lastStart)}{p.lastEnd ? ' → ' + vpFmtPeriod(p.lastEnd) : ''}</td>
                  <td style={tdStyle}>{p.updatedAt ? new Date(p.updatedAt).toLocaleString() : ''}</td>
                  <td style={tdStyle}>
                    <button className="btn" style={{ padding: '2px 10px', fontSize: '0.85rem', color: 'var(--danger-color)' }}
                      onClick={() => setRowDelete(p.license)}>刪除</button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* ===================== 事件紀錄 ===================== */}
      <div className="panel">
        <h3 style={{ marginTop: 0 }}>📜 事件紀錄 (最近 {events.length})</h3>
        <div className="log-container" style={{ overflowX: 'auto', maxHeight: 300 }}>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr>
                <th style={thStyle}>時間</th>
                <th style={thStyle}>車牌</th>
                <th style={thStyle}>事件</th>
                <th style={thStyle}>動作</th>
                <th style={thStyle}>狀態</th>
                <th style={thStyle}>vehicle_id</th>
                <th style={thStyle}>來源</th>
              </tr>
            </thead>
            <tbody>
              {events.length === 0 && (
                <tr><td style={tdStyle} colSpan={7}><span style={{ color: 'var(--text-muted)' }}>尚無事件</span></td></tr>
              )}
              {events.map((e) => (
                <tr key={e.id}>
                  <td style={tdStyle}>{e.timestamp ? new Date(e.timestamp).toLocaleString() : ''}</td>
                  <td style={{ ...tdStyle, fontWeight: 600 }}>{e.license}</td>
                  <td style={tdStyle}>{e.eventName}</td>
                  <td style={tdStyle}>{e.action}</td>
                  <td style={tdStyle}>{statusBadge(e.status)}</td>
                  <td style={{ ...tdStyle, ...codeStyle }}>{e.vehicleId || ''}</td>
                  <td style={tdStyle}>{e.source}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* ===================== 設定 ===================== */}
      <div className="panel">
        <h3 style={{ marginTop: 0 }}>⚙️ API 與連線設定</h3>

        {/* 對外接收路徑 (變更需確認) */}
        <div className="form-group">
          <label>對外接收事件 API 路徑 (目前生效: <span style={codeStyle}>{liveCfg.apiPath}</span>)</label>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            <input type="text" style={{ flex: '1 1 240px' }} value={cfg.apiPath || ''}
              onChange={(e) => setCfg({ ...cfg, apiPath: e.target.value })} placeholder="/api/event" />
            <button className="btn" style={{ backgroundColor: 'var(--warning-color)', borderColor: 'var(--warning-color)', color: '#222' }}
              onClick={requestPathChange} disabled={savingCfg}>變更路徑</button>
          </div>
          <small style={{ color: 'var(--text-muted)' }}>⚠️ 變更路徑會使舊路徑立即失效，外部系統需同步更新呼叫網址。</small>
        </div>

        <div style={{ marginTop: 8, padding: 10, background: 'var(--bg-color)', border: '1px solid var(--border-color)', borderRadius: 6 }}>
          <div style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginBottom: 4 }}>呼叫範例 (中文參數需 URL-encode；瀏覽器網址列會自動編碼):</div>
          <div style={codeStyle}>GET {inboundUrl}</div>
        </div>

        <div className="form-row" style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginTop: 12 }}>
          <div className="form-group" style={{ flex: '1 1 260px' }}>
            <label>轉發目標伺服器 (TLPR baseUrl)</label>
            <input type="text" value={cfg.baseUrl || ''} onChange={(e) => setCfg({ ...cfg, baseUrl: e.target.value })} placeholder="https://192.168.0.249" />
          </div>
          <div className="form-group" style={{ flex: '0 0 140px' }}>
            <label>時區 offset</label>
            <input type="text" value={cfg.tzOffset || ''} onChange={(e) => setCfg({ ...cfg, tzOffset: e.target.value })} placeholder="+08:00" />
          </div>
          <div className="form-group" style={{ flex: '0 0 140px' }}>
            <label>身分 role</label>
            <input type="text" value={cfg.role || ''} onChange={(e) => setCfg({ ...cfg, role: e.target.value })} placeholder="訪客" />
          </div>
        </div>

        <div className="form-row" style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
          <div className="form-group" style={{ flex: '1 1 200px' }}>
            <label>帳號</label>
            <input type="text" value={cfg.username || ''} onChange={(e) => setCfg({ ...cfg, username: e.target.value })} autoComplete="off" />
          </div>
          <div className="form-group" style={{ flex: '1 1 200px' }}>
            <label>密碼 {liveCfg.hasPassword ? '(已設定，留空不變更)' : '(未設定)'}</label>
            <input type="password" value={pwInput} onChange={(e) => setPwInput(e.target.value)} placeholder={liveCfg.hasPassword ? '••••••••' : '請輸入密碼'} autoComplete="new-password" />
          </div>
          <div className="form-group" style={{ flex: '1 1 200px' }}>
            <label>視為「訪客車牌」的事件名稱</label>
            <input type="text" value={cfg.visitorEventName || ''} onChange={(e) => setCfg({ ...cfg, visitorEventName: e.target.value })} placeholder="訪客車牌" />
          </div>
        </div>

        <div className="form-group">
          <label>手動 Access Token {liveCfg.hasManualToken ? '(已設定)' : '(未設定，登入異常時可暫用)'}</label>
          <textarea rows={2} value={tokenInput} onChange={(e) => setTokenInput(e.target.value)}
            placeholder={liveCfg.hasManualToken ? '已設定，輸入新值可覆蓋' : '貼上 access_token (選填)'}
            style={{ width: '100%', fontFamily: 'monospace', fontSize: '0.8rem' }} />
          {liveCfg.hasManualToken && <button className="btn" style={{ padding: '2px 10px', fontSize: '0.85rem' }} onClick={clearToken}>清除 Token</button>}
        </div>

        <div className="form-group">
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }}>
            <input type="checkbox" checked={!!cfg.enabled} onChange={(e) => setCfg({ ...cfg, enabled: e.target.checked })} style={{ width: 'auto' }} />
            啟用對外事件接收
          </label>
        </div>

        <div style={{ display: 'flex', gap: 10, marginTop: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          <button className="btn btn-primary" onClick={() => saveConfig()} disabled={savingCfg}>{savingCfg ? '儲存中...' : '💾 儲存設定'}</button>
          <button className="btn" onClick={handleTest}>🔌 測試連線</button>
          {testResult && <span style={{ fontSize: '0.9rem' }}>{testResult}</span>}
          {cfgFeedback && <span style={{ fontSize: '0.9rem' }}>{cfgFeedback}</span>}
        </div>
      </div>

      {/* 路徑變更確認視窗 */}
      {pathWarn && (
        <ConfirmModal
          message={`確定要將對外接收路徑變更為「${pathWarn}」嗎？舊路徑「${liveCfg.apiPath}」將立即失效，請確認外部系統已同步更新。`}
          onConfirm={confirmPathChange}
          onClose={() => setPathWarn(null)}
        />
      )}

      {/* 列表刪除確認視窗 */}
      {rowDelete && (
        <ConfirmModal
          message={`確定要刪除車牌「${rowDelete}」的訪客預約嗎？此操作會同步刪除 TLPR 伺服器上的車輛資料。`}
          onConfirm={() => {
            socket.emit('visitorPlate:delete', { license: rowDelete }, () => {});
            setRowDelete(null);
          }}
          onClose={() => setRowDelete(null)}
        />
      )}
    </div>
  );
}

// =============================================
// Device List Component
// =============================================
function DeviceList({ devices, selectedId, onSelect, onAdd, onEdit, onDelete, type, deviceStatus, socket, doorStates = {} }) {
  const typeLabel = type === 'access' ? '門禁裝置' : (type === 'etag' ? 'ETag 感應器' : '字幕機');
  const [checking, setChecking] = useState(false);
  const sortedDevices = [...devices].sort((a, b) => Number(a.stationId) - Number(b.stationId));

  const handleCheckStatus = (e) => {
    e.stopPropagation();
    if (!socket || checking) return;
    setChecking(true);
    socket.emit('checkDeviceStatus', () => {
      setChecking(false);
    });
  };

  const getStatusClass = (deviceId) => {
    const s = deviceStatus?.[deviceId];
    if (!s) return 'unknown';
    if (s.online === null) return 'unknown'; // AVT UDP — skip
    return s.online ? 'online' : 'offline';
  };

  const getStatusTitle = (deviceId) => {
    const s = deviceStatus?.[deviceId];
    if (!s) return '未檢測';
    if (s.online === null) return 'UDP 裝置 (無法 TCP 探測)';
    const label = s.online ? '在線' : '離線';
    const time = s.lastCheck ? new Date(s.lastCheck).toLocaleTimeString() : '';
    return `${label} (${time})`;
  };

  const getHwTag = (d) => {
    if (type !== 'textDisplay') return '';
    const hwType = d.extra?.hwType || 'lmc';
    if (hwType === 'avt') {
      const da = d.extra?.displayAddr ?? 0;
      return ` [AVT DA:${da}]`;
    }
    return ' [LM-C]';
  };

  const getDoorStatusIndicator = (d) => {
    if (type !== 'access' || !d.extra?.isPolling) {
      return <span className="door-status-indicator none" title="不偵測" style={{ backgroundColor: '#9ca3af', width: '12px', height: '12px', borderRadius: '50%', display: 'inline-block', marginRight: '6px' }}></span>;
    }
    const doorState = doorStates[d.id];

    // Special handling for DIO Category: Show active counts
    if (d.extra?.category === 'DIO') {
      const pollData = doorState?.pollData;
      if (!pollData || !pollData.diStates) {
        return <span className="door-status-indicator unknown" title="等待偵測" style={{ border: '2px solid #9ca3af', width: '12px', height: '12px', borderRadius: '50%', display: 'inline-block', marginRight: '6px', boxSizing: 'border-box' }}></span>;
      }
      const activeDIs = pollData.diStates.filter(s => s === true).length;
      const activeDOs = pollData.doStates.filter(s => s === true).length;
      
      const getBadgeStyle = (color) => ({
        width: '18px', height: '18px', borderRadius: '50%', border: `2px solid ${color}`,
        color: color, fontSize: '10px', display: 'inline-flex', alignItems: 'center',
        justifyContent: 'center', marginRight: '3px', flexShrink: 0, fontWeight: 'bold',
        transition: 'all 0.2s ease', boxSizing: 'border-box'
      });

      return (
        <span style={{ display: 'inline-flex', alignItems: 'center', marginRight: '4px' }}>
          <span 
            key={`di-${JSON.stringify(pollData.diStates)}`} 
            className="dio-badge-animate-di"
            style={getBadgeStyle(DIO_COLORS.di)} 
            title={`DI 啟動數: ${activeDIs}`}
          >
            {activeDIs}
          </span>
          <span 
            key={`do-${JSON.stringify(pollData.doStates)}`} 
            className="dio-badge-animate-do"
            style={getBadgeStyle(DIO_COLORS.do)} 
            title={`DO 啟動數: ${activeDOs}`}
          >
            {activeDOs}
          </span>
        </span>
      );
    }

    if (!doorState || doorState.state === undefined) {
      return <span className="door-status-indicator unknown" title="等待偵測" style={{ border: '2px solid #9ca3af', width: '12px', height: '12px', borderRadius: '50%', display: 'inline-block', marginRight: '6px', boxSizing: 'border-box' }}></span>;
    }
    if (doorState.state === 0) {
      return <span className="door-status-indicator open" title="開啟" style={{ backgroundColor: '#9ca3af', width: '12px', height: '12px', borderRadius: '50%', display: 'inline-block', marginRight: '6px' }}></span>;
    }
    return <span className="door-status-indicator closed" title="關閉" style={{ backgroundColor: '#22c55e', width: '12px', height: '12px', borderRadius: '50%', display: 'inline-block', marginRight: '6px' }}></span>;
  };

  return (
    <div className="device-list">
      <div className="device-list-header">
        <span>📋 {typeLabel}列表</span>
        <div>
          <button className="btn btn-check-status" onClick={handleCheckStatus} disabled={checking} title="檢測所有裝置連線狀態">
            {checking ? '⏳' : '🔍'} 檢測
          </button>
          <button className="btn btn-add" onClick={onAdd}>＋ 新增</button>
        </div>
      </div>
      {sortedDevices.length === 0 ? (
        <div className="device-empty">尚無裝置，請點擊「新增」</div>
      ) : (
        sortedDevices.map(d => (
            <div
              key={d.id}
              className={`device-item ${selectedId === d.id ? 'selected' : ''}`}
              onClick={() => onSelect(d.id)}
              style={{ alignItems: 'flex-start' }}
            >
              <div className="device-info" style={{ width: '100%' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>
                  {(() => {
                    const isMismatch = type !== 'etag' && parseInt(d.stationId) !== parseInt(d.nodeId);
                    return (
                      <span style={{ 
                        fontSize: '0.9rem', fontWeight: 'bold', width: '36px', 
                        textAlign: 'center', 
                        color: isMismatch ? '#fbbf24' : 'var(--primary-color)',
                        backgroundColor: isMismatch ? 'rgba(251, 191, 36, 0.15)' : 'rgba(139, 148, 158, 0.15)', 
                        borderRadius: '4px',
                        padding: '2px 0', flexShrink: 0,
                        border: isMismatch ? '1px solid rgba(251, 191, 36, 0.3)' : 'none',
                        boxSizing: 'border-box'
                      }} title={isMismatch ? `ID 不匹配: Station(${d.stationId}) != Node(${d.nodeId})` : `Station ID: ${d.stationId}`}>
                        {d.stationId}
                      </span>
                    );
                  })()}
                  <span className={`device-status-dot ${getStatusClass(d.id)}`} title={getStatusTitle(d.id)} style={{ margin: 0 }}></span>
                  {type === 'access' && (
                    <span style={{ fontSize: '0.7rem', backgroundColor: '#eab308', color: '#000', padding: '1px 5px', borderRadius: '3px', fontWeight: 600, flexShrink: 0 }}>
                      {d.extra?.category === 'DIO' ? 'DIO' : '門禁'}
                    </span>
                  )}
                  {type === 'access' && getDoorStatusIndicator(d)}
                  {type === 'textDisplay' && (
                    <span style={{ fontSize: '0.7rem', color: d.extra?.hwType === 'avt' ? '#f59e0b' : '#60a5fa', fontWeight: 600 }}>
                      {getHwTag(d)}
                    </span>
                  )}
                </div>
                <div className="device-name" style={{ marginBottom: '2px', whiteSpace: 'normal', lineHeight: '1.2' }}>
                  {d.name || `${typeLabel}`}
                </div>
                <div className="device-detail">
                  {d.host}:{d.port}
                  {type !== 'etag' && (
                    <> / <span style={{
                      color: parseInt(d.stationId) !== parseInt(d.nodeId) ? '#fbbf24' : 'inherit',
                      fontWeight: parseInt(d.stationId) !== parseInt(d.nodeId) ? 'bold' : 'normal'
                    }}>Node {d.nodeId}</span></>
                  )}
                  {type === 'etag' && d.extra?.isPolling && (
                    <> / <span style={{ color: '#60a5fa' }}>輪詢 {d.extra.pollingInterval || 1000}ms</span></>
                  )}
                </div>
              </div>
            <div className="device-actions">
              <button className="btn-icon" title="編輯" onClick={(e) => { e.stopPropagation(); onEdit(d); }}>✏️</button>
              <button className="btn-icon" title="刪除" onClick={(e) => { e.stopPropagation(); onDelete(d); }}>🗑️</button>
            </div>
          </div>
        ))
      )}
    </div>
  );
}

// =============================================
// Access Control Tab
// =============================================
function AccessTab({ socket, devices, siblingDevices = [], logs, total, commandStatus, deviceStatus, doorStates }) {
  const [selectedDeviceId, setSelectedDeviceId] = useState(devices[0]?.id || null);
  const [showModal, setShowModal] = useState(false);
  const [editDevice, setEditDevice] = useState(null);
  const [deviceToDelete, setDeviceToDelete] = useState(null);

  useEffect(() => {
    if (devices.length > 0 && !devices.find(d => d.id === selectedDeviceId)) {
      setSelectedDeviceId(devices[0].id);
    }
  }, [devices, selectedDeviceId]);

  const selectedDevice = devices.find(d => d.id === selectedDeviceId);

  const handleControl = (action, port = undefined) => {
    if (socket && selectedDeviceId) {
      socket.emit('control', { action, deviceId: selectedDeviceId, port });
    }
  };

  const handleDelete = (device) => {
    setDeviceToDelete(device);
  };

  const confirmDelete = () => {
    if (deviceToDelete) {
      socket.emit('deleteDevice', { id: deviceToDelete.id });
      setDeviceToDelete(null);
    }
  };

  const renderIndicator = (label, isActive) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
      <span style={{ 
        width: '12px', height: '12px', borderRadius: '50%', 
        backgroundColor: isActive === true ? '#22c55e' : (isActive === false ? '#9ca3af' : 'transparent'),
        border: isActive === undefined ? '2px solid #9ca3af' : 'none',
        boxShadow: isActive === true ? '0 0 5px #22c55e' : 'none',
        boxSizing: 'border-box'
      }}></span>
      <span style={{ fontSize: '0.85rem' }}>{label}</span>
    </div>
  );

  const actionLabels = { open: '開門', alwaysOpen: '常開', alwaysClose: '常關' };

  const accessColumns = [
    { key: 'timestamp', label: '時間', accessor: (log) => log.timestamp ? new Date(log.timestamp).toLocaleString() : '' },
    { key: 'station', label: 'Station', accessor: (log) => log.station || '' },
    { key: 'deviceName', label: '裝置', accessor: (log) => log.deviceName || '' },
    { key: 'card', label: '卡號', accessor: (log) => log.card || '' },
    { key: 'soyalCard', label: 'S卡號', accessor: (log) => log.soyalCard || '' },
    { key: 'message', label: '訊息', accessor: (log) => log.message || '' },
    { key: 'code', label: '代碼', accessor: (log) => log.code !== undefined && log.code !== '' ? log.code : '' },
  ];

  return (
    <div className="tab-panel">
      <div className="tab-main">
        <DeviceList
          devices={devices}
          selectedId={selectedDeviceId}
          onSelect={setSelectedDeviceId}
          onAdd={() => { setEditDevice(null); setShowModal(true); }}
          onEdit={(d) => { setEditDevice(d); setShowModal(true); }}
          onDelete={handleDelete}
          type="access"
          deviceStatus={deviceStatus}
          doorStates={doorStates}
          socket={socket}
        />

        {selectedDevice ? (
          <div className="panel control-panel">
            <h2 style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              🔐 
              {(() => {
                const isMismatch = parseInt(selectedDevice.stationId) !== parseInt(selectedDevice.nodeId);
                return (
                  <span style={{ 
                    fontSize: '0.9rem', fontWeight: 'bold', width: '36px', 
                    textAlign: 'center', 
                    color: isMismatch ? '#fbbf24' : 'var(--primary-color)',
                    backgroundColor: isMismatch ? 'rgba(251, 191, 36, 0.15)' : 'rgba(139, 148, 158, 0.15)', 
                    borderRadius: '4px',
                    padding: '2px 0', flexShrink: 0,
                    border: isMismatch ? '1px solid rgba(251, 191, 36, 0.3)' : 'none',
                    boxSizing: 'border-box'
                  }}>
                    {selectedDevice.stationId}
                  </span>
                );
              })()}
              <span style={{ fontSize: '0.7rem', backgroundColor: '#eab308', color: '#000', padding: '1px 5px', borderRadius: '3px', fontWeight: 600, flexShrink: 0 }}>
                {selectedDevice.extra?.category === 'DIO' ? 'DIO' : '門禁'}
              </span>
              {selectedDevice.name} — 控制
            </h2>
            <div className="device-info-bar">
              {selectedDevice.host}:{selectedDevice.port} / <span style={{ 
                color: parseInt(selectedDevice.stationId) !== parseInt(selectedDevice.nodeId) ? '#fbbf24' : 'inherit',
                fontWeight: parseInt(selectedDevice.stationId) !== parseInt(selectedDevice.nodeId) ? 'bold' : 'normal'
              }}>Node {selectedDevice.nodeId}</span>
            </div>

            {selectedDevice.extra?.category === 'DIO' ? (
              <div className="status-indicators" style={{ margin: '15px 0', padding: '15px', backgroundColor: 'var(--bg-lighter)', borderRadius: '8px' }}>
                <h4 style={{ margin: '0 0 10px 0', fontSize: '0.9rem', color: DIO_COLORS.di, borderLeft: `4px solid ${DIO_COLORS.di}`, paddingLeft: '10px' }}>
                  DI 狀態 (Digital Input) {!selectedDevice.extra?.isPolling && <span style={{fontSize: '0.8rem', color: '#ff4d4f'}}> (未啟用輪詢)</span>}
                </h4>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginTop: '5px' }}>
                  {[0, 1, 2, 3, 4, 5, 6, 7].map(i => {
                    const isActive = doorStates[selectedDevice.id]?.pollData?.diStates?.[i];
                    return (
                      <div key={i} style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.85rem', fontWeight: 'bold', padding: '6px 10px', backgroundColor: 'var(--bg-color)', borderRadius: '6px', border: '1px solid var(--border-color)', minHeight: '42px', boxSizing: 'border-box' }}>
                        <span style={{ 
                          width: '10px', height: '10px', borderRadius: '50%', 
                          backgroundColor: isActive === true ? '#22c55e' : (isActive === false ? '#9ca3af' : 'transparent'),
                          border: isActive === undefined ? '2px solid #9ca3af' : 'none',
                          boxShadow: isActive === true ? '0 0 4px #22c55e' : 'none', 
                          flexShrink: 0,
                          boxSizing: 'border-box'
                        }}></span>
                        <span style={{ whiteSpace: 'nowrap' }}>DI {i}</span>
                      </div>
                    );
                  })}
                </div>
              </div>
            ) : (
              selectedDevice.extra?.isPolling && doorStates[selectedDevice.id]?.pollData && (
                <div className="status-indicators" style={{ margin: '15px 0', padding: '15px', backgroundColor: 'var(--bg-lighter)', borderRadius: '8px' }}>
                  <h4 style={{ margin: '0 0 10px 0', fontSize: '0.9rem', color: 'var(--text-muted)' }}>
                    輪詢即時狀態 (Raw: {doorStates[selectedDevice.id].pollData.doorStatusRaw})
                  </h4>
                  <div style={{ display: 'flex', gap: '15px', flexWrap: 'wrap' }}>
                    {renderIndicator('門位狀態 (Sensor)', doorStates[selectedDevice.id].pollData.sensorOpen)}
                    {renderIndicator('出門按鈕 (Exit BTN)', doorStates[selectedDevice.id].pollData.exitButtonPressed)}
                    {renderIndicator('繼電器 (Relay)', doorStates[selectedDevice.id].pollData.relayTriggered)}
                    {renderIndicator('警報 (Alarm)', doorStates[selectedDevice.id].pollData.alarmActive)}
                  </div>
                </div>
              )
            )}

            <div className="controls-container" style={{ marginBottom: '15px' }}>
              {selectedDevice.extra?.category === 'DIO' ? (
                <div style={{ padding: '15px', backgroundColor: 'var(--bg-lighter)', borderRadius: '8px' }}>
                  <h4 style={{ margin: '0 0 10px 0', color: DIO_COLORS.do, fontSize: '0.9rem', borderLeft: `4px solid ${DIO_COLORS.do}`, paddingLeft: '10px' }}>
                    DO 狀態與控制 (Digital Output)
                  </h4>
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: '10px', width: '100%' }}>
                    {[0, 1, 2, 3].map(port => {
                        const isOn = doorStates[selectedDevice.id]?.pollData?.doStates?.[port];
                        return (
                          <div key={port} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 10px', backgroundColor: 'var(--bg-color)', border: '1px solid var(--border-color)', borderRadius: '6px' }}>
                            <span style={{ fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.85rem' }}>
                              <span style={{ 
                                width: '10px', height: '10px', borderRadius: '50%', 
                                backgroundColor: isOn === true ? '#22c55e' : (isOn === false ? '#9ca3af' : 'transparent'), 
                                border: isOn === undefined ? '2px solid #9ca3af' : 'none',
                                boxShadow: isOn === true ? '0 0 5px #22c55e' : 'none',
                                boxSizing: 'border-box'
                              }}></span>
                              DO {port}
                            </span>
                            <div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
                              <button 
                                  className="btn btn-primary" 
                                  style={{ padding: '4px 10px', fontSize: '0.85rem', display: 'flex', alignItems: 'center', gap: '4px', backgroundColor: '#3498db', border: 'none' }} 
                                  onClick={() => handleControl('open', port)}
                                  title="Pulse (瞬間)"
                              >
                                  ⚡ <span style={{fontSize: '0.75rem'}}>{selectedDevice.extra?.doPulseSeconds?.[port] || 8}s</span>
                              </button>
                              <button 
                                  className="btn" 
                                  style={{ padding: '4px 10px', fontSize: '1rem', color: '#2ecc71', borderColor: '#2ecc71', background: 'transparent' }} 
                                  onClick={() => handleControl('alwaysOpen', port)}
                                  title="Latch (長開/保持)"
                              >
                                  🔓
                              </button>
                              <button 
                                  className="btn" 
                                  style={{ padding: '4px 10px', fontSize: '1rem', color: '#e74c3c', borderColor: '#e74c3c', background: 'transparent' }} 
                                  onClick={() => handleControl('alwaysClose', port)}
                                  title="Off (關閉)"
                              >
                                  🔒
                              </button>
                            </div>
                          </div>
                        )
                    })}
                  </div>
                </div>
              ) : (
                <div className="controls-grid">
                  <button className="btn btn-primary" onClick={() => handleControl('open')}>瞬間開門 (Pulse)</button>
                  <button className="btn" onClick={() => handleControl('alwaysOpen')}>常開門 (Latch)</button>
                  <button className="btn" onClick={() => handleControl('alwaysClose')}>恢復常閉 (Off)</button>
                </div>
              )}
            </div>
            {commandStatus && commandStatus.deviceId === selectedDeviceId && (
              <div className={`command-feedback ${commandStatus.status}`}>
                {commandStatus.status === 'sending' && `⏳ ${actionLabels[commandStatus.action] || commandStatus.action} 指令傳送中...`}
                {commandStatus.status === 'success' && `✅ ${actionLabels[commandStatus.action] || commandStatus.action} 指令執行成功`}
                {commandStatus.status === 'error' && `❌ ${actionLabels[commandStatus.action] || commandStatus.action} 指令失敗: ${commandStatus.error}`}
              </div>
            )}
          </div>
        ) : (
          <div className="panel control-panel">
            <div className="empty-state">← 請選擇或新增門禁裝置</div>
          </div>
        )}
      </div>

      {/* Access Logs */}
      <div className="panel log-container">
        <h2>🚪 門禁事件 (Access Events)</h2>
        <LogTable logs={logs} columns={accessColumns} emptyText="等待卡機推送事件..." socket={socket} logType="access" liveTotal={total} />
      </div>

      {showModal && (
        <DeviceModal
          type="access"
          device={editDevice}
          devices={devices}
          siblingDevices={siblingDevices}
          onClose={() => setShowModal(false)}
          socket={socket}
        />
      )}

      {deviceToDelete && (
        <ConfirmModal
          message={`確定要刪除「${deviceToDelete.name}」嗎？`}
          onConfirm={confirmDelete}
          onClose={() => setDeviceToDelete(null)}
        />
      )}
    </div>
  );
}

// =============================================
// ETag Tab
// =============================================
function EtagTab({ socket, devices, siblingDevices = [], logs, total, deviceStatus }) {
  const [selectedDeviceId, setSelectedDeviceId] = useState(devices[0]?.id || null);
  const [showModal, setShowModal] = useState(false);
  const [editDevice, setEditDevice] = useState(null);
  const [deviceToDelete, setDeviceToDelete] = useState(null);

  useEffect(() => {
    if (devices.length > 0 && !devices.find(d => d.id === selectedDeviceId)) {
      setSelectedDeviceId(devices[0].id);
    }
  }, [devices, selectedDeviceId]);

  const selectedDevice = devices.find(d => d.id === selectedDeviceId);

  const handleDelete = (device) => setDeviceToDelete(device);
  const confirmDelete = () => {
    if (deviceToDelete) {
      socket.emit('deleteDevice', { id: deviceToDelete.id });
      setDeviceToDelete(null);
    }
  };

  const etagColumns = [
    { key: 'timestamp', label: '時間', accessor: (log) => log.timestamp ? new Date(log.timestamp).toLocaleString() : '' },
    { key: 'station', label: 'Station', accessor: (log) => log.station || '' },
    { key: 'deviceName', label: '裝置', accessor: (log) => log.deviceName || '' },
    { key: 'card', label: '卡號', accessor: (log) => log.card || '' },
    { key: 'message', label: '訊息', accessor: (log) => log.message || '' },
  ];

  return (
    <div className="tab-panel">
      <div className="tab-main">
        <DeviceList
          devices={devices}
          selectedId={selectedDeviceId}
          onSelect={setSelectedDeviceId}
          onAdd={() => { setEditDevice(null); setShowModal(true); }}
          onEdit={(d) => { setEditDevice(d); setShowModal(true); }}
          onDelete={handleDelete}
          type="etag"
          deviceStatus={deviceStatus}
          socket={socket}
        />

        {selectedDevice ? (
          <div className="panel control-panel">
            <h2 style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
              📻
              <span style={{
                fontSize: '0.9rem', fontWeight: 'bold', width: '36px',
                textAlign: 'center', color: 'var(--primary-color)',
                backgroundColor: 'rgba(139, 148, 158, 0.15)',
                borderRadius: '4px', padding: '2px 0', flexShrink: 0,
              }}>{selectedDevice.stationId}</span>
              {selectedDevice.name} — ETag 感應器
            </h2>
            <div className="device-info-bar">
              {selectedDevice.host}:{selectedDevice.port}
              {selectedDevice.extra?.isPolling ? (
                <> / 輪詢間隔 {selectedDevice.extra.pollingInterval || 1000}ms / Timeout {selectedDevice.extra.timeoutMs || 90}ms</>
              ) : (
                <> / 未啟用輪詢</>
              )}
            </div>
            <div style={{ marginTop: '15px', padding: '12px', backgroundColor: 'var(--bg-lighter)', borderRadius: '8px', fontSize: '0.9rem', color: 'var(--text-muted)' }}>
              感應到的卡號將自動寫入事件記錄、推送至 MQTT (<code>card/trigger</code>)。
            </div>
          </div>
        ) : (
          <div className="panel control-panel">
            <div className="empty-state">← 請選擇或新增 ETag 感應器</div>
          </div>
        )}
      </div>

      <div className="panel log-container">
        <h2>📻 ETag 事件</h2>
        <LogTable logs={logs} columns={etagColumns} emptyText="等待 ETag 感應卡號..." socket={socket} logType="etag" liveTotal={total} />
      </div>

      {showModal && (
        <DeviceModal
          type="etag"
          device={editDevice}
          devices={devices}
          siblingDevices={siblingDevices}
          onClose={() => setShowModal(false)}
          socket={socket}
        />
      )}

      {deviceToDelete && (
        <ConfirmModal
          message={`確定要刪除「${deviceToDelete.name}」嗎？`}
          onConfirm={confirmDelete}
          onClose={() => setDeviceToDelete(null)}
        />
      )}
    </div>
  );
}

// =============================================
// Text Display Tab
// =============================================
function TextDisplayTab({ socket, devices, logs, total, status, deviceStatus }) {
  const [selectedDeviceId, setSelectedDeviceId] = useState(devices[0]?.id || null);
  const [showModal, setShowModal] = useState(false);
  const [editDevice, setEditDevice] = useState(null);
  const [deviceToDelete, setDeviceToDelete] = useState(null);

  useEffect(() => {
    if (devices.length > 0 && !devices.find(d => d.id === selectedDeviceId)) {
      setSelectedDeviceId(devices[0].id);
    }
  }, [devices, selectedDeviceId]);

  const selectedDevice = devices.find(d => d.id === selectedDeviceId);

  const handleDelete = (device) => {
    setDeviceToDelete(device);
  };

  const confirmDelete = () => {
    if (deviceToDelete) {
      socket.emit('deleteDevice', { id: deviceToDelete.id });
      setDeviceToDelete(null);
    }
  };

  const textDisplayColumns = [
    { key: 'timestamp', label: '時間', accessor: (log) => log.timestamp ? new Date(log.timestamp).toLocaleString() : '' },
    { key: 'station', label: 'Station', accessor: (log) => log.station || '' },
    { key: 'deviceName', label: '裝置', accessor: (log) => log.deviceName || '' },
    { key: 'mode', label: '模式', accessor: (log) => log.mode || '' },
    { key: 'message', label: '訊息', accessor: (log) => log.message || '' },
  ];

  return (
    <div className="tab-panel">
      <div className="tab-main">
        <DeviceList
          devices={devices}
          selectedId={selectedDeviceId}
          onSelect={setSelectedDeviceId}
          onAdd={() => { setEditDevice(null); setShowModal(true); }}
          onEdit={(d) => { setEditDevice(d); setShowModal(true); }}
          onDelete={handleDelete}
          type="textDisplay"
          deviceStatus={deviceStatus}
          socket={socket}
        />

        {selectedDevice ? (
          <TextDisplayControl socket={socket} device={selectedDevice} status={status} />
        ) : (
          <div className="panel control-panel">
            <div className="empty-state">← 請選擇或新增字幕機裝置</div>
          </div>
        )}
      </div>

      {/* Text Display Logs */}
      <div className="panel log-container">
        <h2>📺 字幕機記錄 (Text Display Log)</h2>
        <LogTable logs={logs} columns={textDisplayColumns} emptyText="尚無字幕機操作記錄..." socket={socket} logType="textDisplay" liveTotal={total} />
      </div>

      {showModal && (
        <DeviceModal
          type="textDisplay"
          device={editDevice}
          devices={devices}
          onClose={() => setShowModal(false)}
          socket={socket}
        />
      )}

      {deviceToDelete && (
        <ConfirmModal
          message={`確定要刪除「${deviceToDelete.name}」嗎？`}
          onConfirm={confirmDelete}
          onClose={() => setDeviceToDelete(null)}
        />
      )}
    </div>
  );
}

// =============================================
// Text Display Control Panel
// =============================================
function TextDisplayControl({ socket, device, status }) {
  const [text, setText] = useState('');
  const [seconds, setSeconds] = useState(10);
  const [speed, setSpeed] = useState(0);
  const [color, setColor] = useState('紅');
  const [font, setFont] = useState('16x16');

  const isAvt = device.extra?.hwType === 'avt';
  const maxChars = device.extra?.maxChars ?? 4;
  const currentWidth = calculateTextWidth(text.trim());

  const handleSend = (mode) => {
    if (!socket || !text.trim()) return;
    let sendText = text.trim();
    if (!isAvt && mode !== 'marquee') {
      sendText = truncateText(sendText, maxChars);
    }
    socket.emit('textDisplay', { mode, text: sendText, seconds, speed, color, font, deviceId: device.id });
  };

  const handleReset = () => {
    if (!socket) return;
    socket.emit('textDisplayReset', { deviceId: device.id });
  };

  const modeLabels = { interrupt: '插播', fixed: '單幕固定', marquee: '跑馬燈', reset: '重置' };

  const colorOptions = [
    { value: '紅', label: '🔴 紅' },
    { value: '綠', label: '🟢 綠' },
    { value: '黃', label: '🟡 黃' },
    { value: '藍', label: '🔵 藍' },
    { value: '紫', label: '🟣 紫' },
    { value: '青', label: '🩵 青' },
    { value: '白', label: '⚪ 白' },
  ];

  // AVT only supports red, green, yellow, white
  const avtColorOptions = [
    { value: '紅', label: '🔴 紅' },
    { value: '綠', label: '🟢 綠' },
    { value: '黃', label: '🟡 黃' },
    { value: '白', label: '⚪ 白' },
  ];

  const fontOptions = [
    { value: '8x8', label: '8×8 (英數)' },
    { value: '12x12', label: '12×12 (英數)' },
    { value: '16x16', label: '16×16 (中文)' },
    { value: '24x24', label: '24×24' },
    { value: '32x32', label: '32×32' },
  ];

  const secondsOptions = [];
  for (let s = 10; s <= 90; s += 5) secondsOptions.push(s);

  const displayAddr = device.extra?.displayAddr ?? 0;

  return (
    <div className="panel control-panel">
      <h2 style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
        📺 
        {(() => {
          const isMismatch = parseInt(device.stationId) !== parseInt(device.nodeId);
          return (
            <span style={{ 
              fontSize: '0.9rem', fontWeight: 'bold', width: '36px', 
              textAlign: 'center', 
              color: isMismatch ? '#fbbf24' : 'var(--primary-color)',
              backgroundColor: isMismatch ? 'rgba(251, 191, 36, 0.15)' : 'rgba(139, 148, 158, 0.15)', 
              borderRadius: '4px',
              padding: '2px 0', flexShrink: 0,
              border: isMismatch ? '1px solid rgba(251, 191, 36, 0.3)' : 'none',
              boxSizing: 'border-box'
            }}>
              {device.stationId}
            </span>
          );
        })()}
        <span style={{ fontSize: '0.7rem', backgroundColor: '#eab308', color: '#000', padding: '1px 5px', borderRadius: '3px', fontWeight: 600, flexShrink: 0 }}>
          字幕機
        </span>
        {device.name} — 控制
      </h2>
      <div className="device-info-bar">
        {device.host}:{device.port} / <span style={{ 
          color: parseInt(device.stationId) !== parseInt(device.nodeId) ? '#fbbf24' : 'inherit',
          fontWeight: parseInt(device.stationId) !== parseInt(device.nodeId) ? 'bold' : 'normal'
        }}>Node {device.nodeId}</span>
        {isAvt && <span style={{ color: '#f59e0b', fontWeight: 600, marginLeft: '8px' }}>[AVT DA:{displayAddr}]</span>}
        {!isAvt && <span style={{ color: '#60a5fa', fontWeight: 600, marginLeft: '8px' }}>[LM-C]</span>}
      </div>

      <div className="form-group">
        <label>顯示文字</label>
        <textarea
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="輸入要顯示的文字..."
          rows={2}
          style={{
            width: '100%', padding: '8px 12px',
            backgroundColor: 'var(--bg-color)', border: '1px solid var(--border-color)',
            borderRadius: '6px', color: 'var(--text-color)', fontSize: '1rem',
            boxSizing: 'border-box', resize: 'vertical', fontFamily: 'inherit'
          }}
        />
        {!isAvt && (
          <div style={{
            fontSize: '0.8rem', marginTop: '4px',
            color: currentWidth > maxChars ? '#ef4444' : 'var(--text-muted)'
          }}>
            目前字數: {currentWidth} / 上限: {maxChars}（插播/單幕固定適用，跑馬燈不限）
          </div>
        )}
      </div>

      <div className="td-params">
        <div className="form-group">
          <label>顏色</label>
          <select value={color} onChange={(e) => setColor(e.target.value)}
            style={{ width: '100%', padding: '8px', backgroundColor: 'var(--bg-color)',
              border: '1px solid var(--border-color)', borderRadius: '6px',
              color: 'var(--text-color)', fontSize: '0.95rem' }}>
            {(isAvt ? avtColorOptions : colorOptions).map(c => <option key={c.value} value={c.value}>{c.label}</option>)}
          </select>
        </div>

        {!isAvt && (
          <div className="form-group">
            <label>字型</label>
            <select value={font} onChange={(e) => setFont(e.target.value)}
              style={{ width: '100%', padding: '8px', backgroundColor: 'var(--bg-color)',
                border: '1px solid var(--border-color)', borderRadius: '6px',
                color: 'var(--text-color)', fontSize: '0.95rem' }}>
              {fontOptions.map(f => <option key={f.value} value={f.value}>{f.label}</option>)}
            </select>
          </div>
        )}

        {!isAvt && (
          <div className="form-group">
            <label>秒數</label>
            <select value={seconds} onChange={(e) => setSeconds(Number(e.target.value))}
              style={{ width: '100%', padding: '8px', backgroundColor: 'var(--bg-color)',
                border: '1px solid var(--border-color)', borderRadius: '6px',
                color: 'var(--text-color)', fontSize: '0.95rem' }}>
              {secondsOptions.map(s => <option key={s} value={s}>{s} 秒</option>)}
            </select>
          </div>
        )}

        <div className="form-group">
          <label>速度{isAvt ? ' (1=最快)' : ''}</label>
          <select value={speed} onChange={(e) => setSpeed(Number(e.target.value))}
            style={{ width: '100%', padding: '8px', backgroundColor: 'var(--bg-color)',
              border: '1px solid var(--border-color)', borderRadius: '6px',
              color: 'var(--text-color)', fontSize: '0.95rem' }}>
            {Array.from({length: isAvt ? 32 : 16}, (_, i) => (
              <option key={i} value={i}>{i}{!isAvt && i === 4 ? ' (標準)' : ''}{isAvt && i === 1 ? ' (標準)' : ''}</option>
            ))}
          </select>
        </div>
      </div>

      <div className="td-actions">
        {!isAvt && (
          <button className="btn btn-td btn-interrupt" onClick={() => handleSend('interrupt')} disabled={!text.trim()}>
            ⚡ 插播
          </button>
        )}
        <button className="btn btn-td btn-fixed" onClick={() => handleSend('fixed')} disabled={!text.trim()}>
          📌 單幕固定
        </button>
        <button className="btn btn-td btn-marquee" onClick={() => handleSend('marquee')} disabled={!text.trim()}>
          🔄 跑馬燈
        </button>
        <button className="btn btn-td btn-reset" onClick={handleReset}>
          ↩️ {isAvt ? '停止' : '重置'}
        </button>
      </div>

      {status && status.deviceId === device.id && (
        <div className={`command-feedback ${status.status}`}>
          {status.status === 'sending' && `⏳ ${modeLabels[status.mode] || status.mode} 指令傳送中...`}
          {status.status === 'success' && `✅ ${modeLabels[status.mode] || status.mode} 指令執行成功`}
          {status.status === 'error' && `❌ ${modeLabels[status.mode] || status.mode} 指令失敗: ${status.error}`}
        </div>
      )}
    </div>
  );
}

// =============================================
// MQTT Tab
// =============================================
function MqttTab({ socket, settings, setSettings, status, logs, total, accessDevices, textDisplayDevices }) {
  const [localSettings, setLocalSettings] = useState({ ...settings });
  const [saving, setSaving] = useState(false);
  const [testFeedback, setTestFeedback] = useState(null);

  // Door test state
  const [doorStation, setDoorStation] = useState('');
  const [doorAction, setDoorAction] = useState(1);
  const [doorIdx, setDoorIdx] = useState(0);

  // Display test state
  const [displayStation, setDisplayStation] = useState('');
  const [displayText, setDisplayText] = useState('');
  const [displayType, setDisplayType] = useState(1);
  const [displaySeconds, setDisplaySeconds] = useState(10);
  const [displaySpeed, setDisplaySpeed] = useState(0);

  useEffect(() => {
    setLocalSettings({ ...settings });
  }, [settings]);

  const handleSettingsChange = (field, value) => {
    setLocalSettings(prev => ({ ...prev, [field]: value }));
  };

  const saveSettings = () => {
    setSaving(true);
    socket.emit('saveMqttSettings', localSettings, (res) => {
      setSaving(false);
      if (res.success) {
        setSettings(localSettings);
        showFeedback('✅ 設定已儲存');
      } else {
        showFeedback('❌ 儲存失敗: ' + res.error);
      }
    });
  };

  const handleConnect = () => {
    socket.emit('mqttConnect', (res) => {
      if (!res.success) showFeedback('❌ ' + res.error);
    });
  };

  const handleDisconnect = () => {
    socket.emit('mqttDisconnect', () => {
      showFeedback('已斷線');
    });
  };

  const showFeedback = (msg) => {
    setTestFeedback(msg);
    setTimeout(() => setTestFeedback(null), 3000);
  };

  // Door test
  const testDoor = () => {
    const station = Number(doorStation);
    if (!station) { showFeedback('❌ 請選擇門禁裝置'); return; }
    
    const selectedDevice = accessDevices.find(d => String(d.stationId) === String(doorStation));
    const isDio = selectedDevice?.extra?.category === 'DIO';
    
    const payload = { id: crypto.randomUUID(), station, state: doorAction };
    if (isDio) {
      payload.idx = doorIdx;
    }
    
    socket.emit('mqttPublishTest', { topic: 'door/do', payload }, (res) => {
      showFeedback(res.success ? '✅ 門禁指令已發布' : '❌ ' + res.error);
    });
  };

  // Display test
  const testDisplay = () => {
    const station = Number(displayStation);
    if (!station) { showFeedback('❌ 請選擇字幕機裝置'); return; }
    if (!displayText.trim()) { showFeedback('❌ 請輸入文字'); return; }
    const payload = { id: crypto.randomUUID(), station, state: displayText, extra: { type: displayType, seconds: displaySeconds, speed: displaySpeed } };
    socket.emit('mqttPublishTest', { topic: 'display/do', payload }, (res) => {
      showFeedback(res.success ? '✅ 字幕指令已發布' : '❌ ' + res.error);
    });
  };

  // List query
  const testList = (topic) => {
    const payload = { id: crypto.randomUUID() };
    socket.emit('mqttPublishTest', { topic, payload }, (res) => {
      showFeedback(res.success ? `✅ ${topic} 查詢已發布` : '❌ ' + res.error);
    });
  };

  const statusColors = {
    connected: 'var(--success-color)',
    connecting: 'var(--warning-color)',
    disconnected: 'var(--text-muted)',
    error: 'var(--danger-color)',
  };
  const statusLabels = {
    connected: '已連線',
    connecting: '連線中...',
    disconnected: '未連線',
    error: '連線錯誤',
  };

  const protocolHints = {
    mqtt: '192.168.0.100:1883',
    mqtts: '192.168.0.100:8883',
    ws: '192.168.0.100:9999/ws',
    wss: '192.168.0.100:9999/ws',
  };

  const mqttColumns = [
    { key: 'timestamp', label: '時間', accessor: (log) => log.timestamp ? new Date(log.timestamp).toLocaleString() : '' },
    { key: 'category', label: '類別', accessor: (log) => log.category || '' },
    { key: 'station', label: 'Station', accessor: (log) => log.station || '' },
    { key: 'topic', label: 'Topic', accessor: (log) => log.topic || '' },
    { key: 'message', label: '訊息', accessor: (log) => log.message || '' },
  ];

  const selectStyle = {
    width: '100%', padding: '8px 12px',
    backgroundColor: 'var(--bg-color)', border: '1px solid var(--border-color)',
    borderRadius: '6px', color: 'var(--text-color)', fontSize: '0.95rem',
  };

  return (
    <div className="tab-panel">
      <div className="mqtt-top-grid">
        {/* Settings */}
        <div className="panel mqtt-settings-panel">
          <h2>⚙️ MQTT 設定</h2>

          <div className="form-group">
            <label>通訊協定</label>
            <select value={localSettings.protocol || 'mqtt'} onChange={(e) => handleSettingsChange('protocol', e.target.value)} style={selectStyle}>
              <option value="mqtt">mqtt://</option>
              <option value="mqtts">mqtts://</option>
              <option value="ws">ws://</option>
              <option value="wss">wss://</option>
            </select>
          </div>

          <div className="form-group">
            <label>Host + Port</label>
            <input type="text" value={localSettings.host || ''} onChange={(e) => handleSettingsChange('host', e.target.value)}
              placeholder={protocolHints[localSettings.protocol || 'mqtt']} />
          </div>

          <div className="form-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
            <div className="form-group">
              <label>帳號 (Name)</label>
              <input type="text" value={localSettings.username || ''} onChange={(e) => handleSettingsChange('username', e.target.value)} placeholder="選填" />
            </div>
            <div className="form-group">
              <label>密碼 (Pass)</label>
              <input type="password" value={localSettings.password || ''} onChange={(e) => handleSettingsChange('password', e.target.value)} placeholder="選填" />
            </div>
          </div>

          <div className="form-group" style={{ marginTop: '15px' }}>
            <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', gap: '8px' }}>
              <input type="checkbox" checked={localSettings.baBridgeEnabled || false} onChange={(e) => handleSettingsChange('baBridgeEnabled', e.target.checked)} />
              啟用 BA MQTT 橋接功能
            </label>
          </div>

          {localSettings.baBridgeEnabled && (
            <div style={{ marginTop: '10px', padding: '15px', backgroundColor: 'var(--bg-color)', borderRadius: '6px', border: '1px solid var(--border-color)' }}>
              <h3 style={{ margin: '0 0 10px 0', fontSize: '0.95rem' }}>BA MQTT 橋接設定</h3>
              
              <div className="form-group">
                <label>BA MQTT Host (含 protocol://host:port)</label>
                <input type="text" value={localSettings.baMqttHost || ''} onChange={(e) => handleSettingsChange('baMqttHost', e.target.value)} placeholder="mqtt://192.168.1.10:1883" />
              </div>

              <div className="form-row" style={{ gridTemplateColumns: '1fr 1fr' }}>
                <div className="form-group">
                  <label>帳號 (選填)</label>
                  <input type="text" value={localSettings.baMqttUsername || ''} onChange={(e) => handleSettingsChange('baMqttUsername', e.target.value)} />
                </div>
                <div className="form-group">
                  <label>密碼 (選填)</label>
                  <input type="password" value={localSettings.baMqttPassword || ''} onChange={(e) => handleSettingsChange('baMqttPassword', e.target.value)} />
                </div>
              </div>

              <div className="form-group">
                <label>BA API URL (狀態回報)</label>
                <input type="text" value={localSettings.baApiUrl || ''} onChange={(e) => handleSettingsChange('baApiUrl', e.target.value)} placeholder="http://192.168.1.10/ezcon/api/updateMainState" />
              </div>
            </div>
          )}

          <button className="btn btn-primary" onClick={saveSettings} disabled={saving} style={{ width: '100%', marginTop: '15px' }}>
            {saving ? '⏳ 儲存中...' : '💾 儲存設定'}
          </button>
        </div>

        {/* Connection Status + Control */}
        <div className="panel mqtt-status-panel">
          <h2>📡 連線狀態</h2>

          <div className="mqtt-status-display">
            <span className="mqtt-status-dot" style={{ backgroundColor: statusColors[status.status] || 'gray' }}></span>
            <span className="mqtt-status-text" style={{ color: statusColors[status.status] || 'gray' }}>
              {statusLabels[status.status] || status.status}
            </span>
          </div>

          {status.config && status.config.host && (
            <div className="mqtt-status-info">
              {status.config.protocol}://{status.config.host}
              {status.config.username && ` (${status.config.username})`}
            </div>
          )}

          {status.lastError && (
            <div className="mqtt-status-error">
              ⚠️ {status.lastError}
            </div>
          )}

          <div className="mqtt-connect-actions">
            <button className="btn btn-primary" onClick={handleConnect}
              disabled={status.status === 'connected' || status.status === 'connecting'}>
              🔌 連線
            </button>
            <button className="btn" onClick={handleDisconnect}
              disabled={status.status === 'disconnected'}>
              ⛔ 斷線
            </button>
          </div>

          {testFeedback && (
            <div className="command-feedback" style={{ marginTop: '12px', background: 'rgba(88,166,255,0.1)', padding: '8px', borderRadius: '6px', textAlign: 'center' }}>
              {testFeedback}
            </div>
          )}
        </div>
      </div>

      {/* Function Test */}
      <div className="panel mqtt-test-panel">
        <h2>🧪 功能測試 (透過 MQTT 發布)</h2>
        <div className="mqtt-test-grid">
          {/* Door Test */}
          <div className="mqtt-test-section">
            <h3>🚪 門禁控制</h3>
            <div className="form-group">
              <label>門禁裝置</label>
              <select value={doorStation} onChange={(e) => setDoorStation(e.target.value)} style={selectStyle}>
                <option value="">-- 選擇 --</option>
                {accessDevices.map(d => (
                  <option key={d.id} value={d.stationId}>Station {d.stationId} - {d.name}</option>
                ))}
              </select>
            </div>
            <div className="form-group">
              <label>動作</label>
              <select value={doorAction} onChange={(e) => setDoorAction(Number(e.target.value))} style={selectStyle}>
                <option value={1}>開門</option>
                <option value={2}>常開</option>
                <option value={3}>常閉</option>
              </select>
            </div>
            {(() => {
              const selectedDevice = accessDevices.find(d => String(d.stationId) === String(doorStation));
              if (selectedDevice?.extra?.category === 'DIO') {
                return (
                  <div className="form-group">
                    <label>輸出埠 (DO)</label>
                    <select value={doorIdx} onChange={(e) => setDoorIdx(Number(e.target.value))} style={selectStyle}>
                      {Array.from({ length: 8 }, (_, i) => (
                        <option key={i} value={i}>DO {i}</option>
                      ))}
                    </select>
                  </div>
                );
              }
              return null;
            })()}
            <button className="btn btn-primary" onClick={testDoor} disabled={status.status !== 'connected'}>
              ▶️ 發送門禁指令
            </button>
          </div>

          {/* Display Test */}
          <div className="mqtt-test-section">
            <h3>📺 字幕機控制</h3>
            <div className="form-group">
              <label>字幕機裝置</label>
              <select value={displayStation} onChange={(e) => setDisplayStation(e.target.value)} style={selectStyle}>
                <option value="">-- 選擇 --</option>
                {textDisplayDevices.map(d => (
                  <option key={d.id} value={d.stationId}>Station {d.stationId} - {d.name}</option>
                ))}
              </select>
            </div>
            <div className="form-group">
              <label>文字</label>
              <input type="text" value={displayText} onChange={(e) => setDisplayText(e.target.value)} placeholder="輸入顯示文字" />
            </div>
            <div className="form-row" style={{ gridTemplateColumns: displayType === 1 ? '1fr 1fr 1fr' : '1fr 1fr' }}>
              <div className="form-group">
                <label>模式</label>
                <select value={displayType} onChange={(e) => setDisplayType(Number(e.target.value))} style={selectStyle}>
                  <option value={1}>跑馬燈</option>
                  <option value={2}>單幕固定</option>
                  <option value={3}>插播</option>
                </select>
              </div>
              <div className="form-group">
                <label>秒數</label>
                <select value={displaySeconds} onChange={(e) => setDisplaySeconds(Number(e.target.value))} style={selectStyle}>
                  {[10,15,20,25,30].map(s => <option key={s} value={s}>{s} 秒</option>)}
                </select>
              </div>
              {displayType === 1 && (
                <div className="form-group">
                  <label>速度</label>
                  <select value={displaySpeed} onChange={(e) => setDisplaySpeed(Number(e.target.value))} style={selectStyle}>
                    {Array.from({length: 16}, (_, i) => (
                      <option key={i} value={i}>{i}{i === 4 ? ' (標準)' : ''}</option>
                    ))}
                  </select>
                </div>
              )}
            </div>
            <button className="btn btn-primary" onClick={testDisplay} disabled={status.status !== 'connected'}>
              ▶️ 發送字幕指令
            </button>
          </div>

          {/* List Queries */}
          <div className="mqtt-test-section">
            <h3>📋 列表查詢</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
              <button className="btn" onClick={() => testList('door/list')} disabled={status.status !== 'connected'}>
                查詢門禁列表
              </button>
              <button className="btn" onClick={() => testList('display/list')} disabled={status.status !== 'connected'}>
                查詢字幕機列表
              </button>
              <button className="btn" onClick={() => testList('card/list')} disabled={status.status !== 'connected'}>
                查詢刷卡裝置列表
              </button>
              <button className="btn" onClick={() => testList('di/list')} disabled={status.status !== 'connected'}>
                查詢 DI 列表
              </button>
            </div>
          </div>
        </div>
      </div>

      {/* MQTT Logs */}
      <div className="panel log-container">
        <h2>📋 MQTT 動作記錄</h2>
        <LogTable logs={logs} columns={mqttColumns} emptyText="尚無 MQTT 動作記錄..." socket={socket} logType="mqtt" liveTotal={total} />
      </div>
    </div>
  );
}

// =============================================
// Settings Tab
// =============================================
function SettingsTab({ authToken, onLogout }) {
  const [loading, setLoading] = useState(true);
  const [settings, setSettings] = useState({ loginEnabled: false, jwtExpiryHours: 1, logLevel: 'info' });
  const [users, setUsers] = useState([]);
  
  // Modals state
  const [showUserModal, setShowUserModal] = useState(false);
  const [editUser, setEditUser] = useState(null);
  const [userForm, setUserForm] = useState({ userId: '', username: '', password: '' });
  const [savingSettings, setSavingSettings] = useState(false);

  const fetchSettings = useCallback(() => {
    fetch('/api/settings', { headers: { 'Authorization': `Bearer ${authToken}` } })
      .then(res => res.json())
      .then(data => setSettings(data))
      .catch(err => console.error(err));
  }, [authToken]);

  const fetchUsers = useCallback(() => {
    fetch('/api/users', { headers: { 'Authorization': `Bearer ${authToken}` } })
      .then(res => res.json())
      .then(data => setUsers(data))
      .catch(err => console.error(err))
      .finally(() => setLoading(false));
  }, [authToken]);

  useEffect(() => {
    if (authToken) {
      fetchSettings();
      fetchUsers();
    }
  }, [authToken, fetchSettings, fetchUsers]);

  const handleSettingsChange = (field, value) => {
    setSettings(prev => ({ ...prev, [field]: value }));
  };

  const saveSettings = () => {
    setSavingSettings(true);
    fetch('/api/settings', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify(settings)
    })
      .then(res => res.json())
      .then(data => {
        setSavingSettings(false);
        if (data.success) {
          alert('設定已儲存');
        }
      })
      .catch(err => {
        setSavingSettings(false);
        alert('儲存失敗');
      });
  };

  const handleRestart = () => {
    if (confirm('確定要重啟後端服務嗎？這將會中斷目前的連線並在數秒內恢復。')) {
      fetch('/api/restart', {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${authToken}` }
      })
      .then(() => {
        alert('已送出重啟指令，請稍候重整頁面...');
        setTimeout(() => window.location.reload(), 3000);
      })
      .catch(err => alert('執行重啟失敗'));
    }
  };

  const handleUserSubmit = (e) => {
    e.preventDefault();
    if (!userForm.userId || !userForm.username) return alert('帳號與名稱必填');
    if (!editUser && !userForm.password) return alert('新增使用者必須輸入密碼');

    const method = editUser ? 'PUT' : 'POST';
    const url = editUser ? `/api/users/${editUser.id}` : '/api/users';

    fetch(url, {
      method,
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` },
      body: JSON.stringify(userForm)
    })
      .then(res => res.json())
      .then(data => {
        if (data.success) {
          setShowUserModal(false);
          fetchUsers();
        } else {
          alert(data.error || '儲存失敗');
        }
      })
      .catch(err => alert('網路錯誤'));
  };

  const handleDeleteUser = (id) => {
    if (confirm('確定刪除此使用者？')) {
      fetch(`/api/users/${id}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${authToken}` }
      })
        .then(res => res.json())
        .then(data => {
          if (data.success) fetchUsers();
          else alert(data.error || '刪除失敗');
        })
        .catch(err => alert('網路錯誤'));
    }
  };

  const openAddUser = () => {
    setEditUser(null);
    setUserForm({ userId: '', username: '', password: '' });
    setShowUserModal(true);
  };

  const openEditUser = (u) => {
    setEditUser(u);
    setUserForm({ userId: u.userId, username: u.username, password: '' });
    setShowUserModal(true);
  };

  if (loading) return <div style={{ color: 'white', padding: '20px' }}>讀取中...</div>;

  return (
    <div className="tab-panel flex-row">
      {/* Settings Column */}
      <div className="panel" style={{ flex: 1, height: 'fit-content' }}>
        <h2>⚙️ 系統設定</h2>
        <div style={{ padding: '20px', backgroundColor: 'var(--bg-lighter)', borderRadius: '8px' }}>
          
          <div className="form-group" style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
            <input 
              type="checkbox" 
              id="enableLogin"
              checked={settings.loginEnabled} 
              onChange={e => handleSettingsChange('loginEnabled', e.target.checked)}
              style={{ width: '20px', height: '20px', accentColor: 'var(--primary-color)' }}
            />
            <label htmlFor="enableLogin" style={{ marginBottom: 0, fontSize: '1.05rem', cursor: 'pointer' }}>啟用登入管控</label>
            <span style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginLeft: '10px' }}>
              若勾選且無任何使用者，可使用 admin/admin 登入。
            </span>
          </div>

          <div className="form-group">
            <label>登入有效時間 (小時: 1~96)</label>
            <input 
              type="number" 
              min="1" 
              max="96" 
              value={settings.jwtExpiryHours} 
              onChange={e => handleSettingsChange('jwtExpiryHours', Number(e.target.value))} 
            />
          </div>

          <div className="form-group">
            <label>Console Log Level</label>
            <select 
              value={settings.logLevel} 
              onChange={e => handleSettingsChange('logLevel', e.target.value)}
              style={{ width: '100%', padding: '8px', backgroundColor: 'var(--bg-color)', border: '1px solid var(--border-color)', color: 'var(--text-color)', borderRadius: '6px' }}
            >
              <option value="error">Error (僅錯誤)</option>
              <option value="warn">Warn (警告與錯誤)</option>
              <option value="info">Info (一般資訊)</option>
              <option value="debug">Debug (除錯資訊)</option>
              <option value="hex">Hex (封包除錯)</option>
            </select>
          </div>

          <div style={{ marginTop: '25px', display: 'flex', alignItems: 'center', gap: '15px' }}>
            <button className="btn btn-primary" onClick={saveSettings} disabled={savingSettings}>
              {savingSettings ? '儲存中...' : '💾 儲存設定'}
            </button>
            <button className="btn" onClick={handleRestart} style={{ backgroundColor: 'var(--danger-color)', borderColor: 'var(--danger-color)', color: 'white' }}>
              🔄 重設程序
            </button>
            <button className="btn" onClick={onLogout} style={{ marginLeft: 'auto' }}>
              🚪 登出
            </button>
          </div>

        </div>
      </div>

      {/* Users Column */}
      <div className="panel" style={{ flex: 1 }}>
        <div className="device-list-header">
          <span>👥 使用者列表</span>
          <button className="btn btn-add" onClick={openAddUser}>＋ 新增使用者</button>
        </div>
        
        <div style={{ marginTop: '15px' }}>
          {users.length === 0 ? (
            <div className="device-empty" style={{ backgroundColor: 'var(--bg-lighter)' }}>尚無使用者</div>
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
              {users.map(u => (
                <div key={u.id} className="device-item">
                  <div className="device-info">
                    <span className="device-name">{u.username}</span>
                    <span className="device-detail">帳號: {u.userId}</span>
                  </div>
                  <div className="device-actions">
                    <button className="btn-icon" title="編輯" onClick={() => openEditUser(u)}>✏️</button>
                    <button className="btn-icon" title="刪除" onClick={() => handleDeleteUser(u.id)}>🗑️</button>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* User Modal */}
      {showUserModal && (
        <div className="modal-overlay" onClick={() => setShowUserModal(false)}>
          <div className="modal-content" onClick={e => e.stopPropagation()}>
            <h3>{editUser ? '編輯使用者' : '新增使用者'}</h3>
            <form onSubmit={handleUserSubmit}>
              <div className="form-group">
                <label>帳號 (User ID)</label>
                <input type="text" value={userForm.userId} onChange={e => setUserForm({...userForm, userId: e.target.value})} required disabled={!!editUser && userForm.userId === 'admin'} />
              </div>
              <div className="form-group">
                <label>名稱 (Username)</label>
                <input type="text" value={userForm.username} onChange={e => setUserForm({...userForm, username: e.target.value})} required />
              </div>
              <div className="form-group">
                <label>密碼 (Password) {editUser && <span style={{color:'var(--text-muted)'}}>(不修改請留空)</span>}</label>
                <input type="password" value={userForm.password} onChange={e => setUserForm({...userForm, password: e.target.value})} {...(!editUser ? { required: true } : {})} />
              </div>
              <div className="modal-actions" style={{ marginTop: '20px' }}>
                <button type="button" className="btn" onClick={() => setShowUserModal(false)}>取消</button>
                <button type="submit" className="btn btn-primary">{editUser ? '更新' : '新增'}</button>
              </div>
            </form>
          </div>
        </div>
      )}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
