import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import {
  ArrowCounterclockwise20Regular as RotateCcw,
  ArrowDownload20Regular as Download,
  ArrowSync20Regular as RefreshCcw,
  Board20Regular as Cpu,
  Bot24Filled as PackageCheck,
  Box20Regular as Box,
  CheckmarkCircle20Filled as CheckCircle2,
  Clipboard20Regular as Clipboard,
  Delete20Regular as Trash2,
  Dismiss20Regular as X,
  ErrorCircle20Filled as XCircle,
  HardDrive20Regular as HardDrive,
  Open20Regular as ExternalLink,
  Play20Filled as Play,
  PlugConnected20Regular as PlugZap,
  Pulse20Regular as Activity,
  Search20Regular as Search,
  Server20Regular as Server,
  Settings20Regular as Settings,
  ShieldCheckmark20Filled as ShieldCheck,
  SpinnerIos20Regular as Loader2,
  Warning20Filled as AlertCircle,
  WifiOff20Regular as WifiOff
} from '@fluentui/react-icons';
import '@fontsource-variable/montserrat';
import {
  DEFAULT_AGENT_BASE_URL,
  DEFAULT_PACKAGE_BASE_URL,
  fetchAgentHealth,
  fetchAgentSystemInfo,
  fetchApplicationDetail,
  fetchApplicationManifest,
  fetchInstalledApps,
  fetchLatestAgentPackage,
  fetchPackageApps,
  fetchTaskComponents,
  fetchTaskLogs,
  fetchTaskStatus,
  getAppOpenUrl,
  joinUrl,
  normalizeUrl,
  queueInstall,
  queueRemove,
  queueUpdate
} from './services/api.js';
import './styles.css';

const SETTINGS_KEY = 'robot-installer-client-settings';
const TERMINAL_TASK_STATUSES = new Set(['success', 'failed', 'cancelled']);
const TASK_STATUS_TONES = {
  queued: 'info',
  running: 'info',
  success: 'success',
  failed: 'danger',
  cancelled: 'warning'
};
const TASK_ACTION_LABELS = {
  install: 'Installing',
  update: 'Updating',
  remove: 'Removing'
};
const AGENT_VERSION_COLLATOR = new Intl.Collator('en', {
  numeric: true,
  sensitivity: 'base'
});

function readSettings() {
  try {
    const parsed = JSON.parse(window.localStorage.getItem(SETTINGS_KEY) || '{}');
    return {
      packageBaseUrl: normalizeUrl(parsed.packageBaseUrl || DEFAULT_PACKAGE_BASE_URL),
      agentBaseUrl: normalizeUrl(parsed.agentBaseUrl || DEFAULT_AGENT_BASE_URL)
    };
  } catch {
    return {
      packageBaseUrl: DEFAULT_PACKAGE_BASE_URL,
      agentBaseUrl: DEFAULT_AGENT_BASE_URL
    };
  }
}

function saveSettings(settings) {
  window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
}

function getErrorMessage(error) {
  return error instanceof Error ? error.message : String(error || 'Có lỗi xảy ra');
}

function copyTextFallback(text) {
  const textarea = document.createElement('textarea');
  textarea.value = text;
  textarea.setAttribute('readonly', '');
  textarea.style.position = 'fixed';
  textarea.style.top = '-1000px';
  textarea.style.opacity = '0';
  document.body.appendChild(textarea);
  textarea.focus();
  textarea.select();
  textarea.setSelectionRange(0, textarea.value.length);

  try {
    return document.execCommand('copy');
  } finally {
    document.body.removeChild(textarea);
  }
}

function isTaskActive(task) {
  return Boolean(task?.taskId && !TERMINAL_TASK_STATUSES.has(task.status));
}

function clampProgress(value) {
  return Math.max(0, Math.min(100, Number(value) || 0));
}

function statusBadgeClass(status) {
  const tone = TASK_STATUS_TONES[status] || 'info';
  return `badge badge-${tone}`;
}

function serviceCheckTone(check) {
  if (check?.healthy) return 'success';
  if (check?.activeState === 'activating' || check?.status === 'checking') return 'warning';
  if (['failed', 'inactive', 'not-found'].includes(check?.status || check?.activeState)) return 'danger';
  return 'muted';
}

function serviceCheckLabel(check) {
  if (check?.readinessStatus === 'failed') return 'Not ready';
  if (check?.healthy) return check.readinessStatus === 'ready' ? 'Active · Ready' : 'Active';
  if (check?.loadState === 'not-found' || check?.status === 'not-found') return 'Not found';
  if (check?.activeState === 'failed') return 'Failed';
  if (check?.activeState === 'inactive') return 'Inactive';
  if (check?.activeState === 'activating') return 'Activating';
  return 'Unknown';
}

function serviceCheckSummary(status, checks = []) {
  if (status === 'checking') return 'Checking services';
  if (status === 'not-applicable') return 'No systemd service';
  if (status === 'unhealthy') {
    const count = checks.filter((check) => !check.healthy).length || checks.length;
    return `${count} service${count === 1 ? '' : 's'} need attention`;
  }
  if (status === 'healthy') {
    return `${checks.length} service${checks.length === 1 ? '' : 's'} active`;
  }
  return '';
}

function formatTaskTime(value) {
  if (!value) return '--';
  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) return value;
  return parsed.toLocaleTimeString([], {
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit'
  });
}

function compareAgentVersions(currentVersion, latestVersion) {
  const current = String(currentVersion || '').trim();
  const latest = String(latestVersion || '').trim();
  if (!current && !latest) return 0;
  if (!current) return -1;
  if (!latest) return 1;
  return AGENT_VERSION_COLLATOR.compare(current, latest);
}

const CLIENT_OS_LABELS = {
  android: 'Android',
  linux: 'Linux',
  macos: 'macOS',
  unknown: 'unknown OS',
  windows: 'Windows'
};

function detectClientOs() {
  if (typeof navigator === 'undefined') return 'unknown';

  const ua = String(navigator.userAgent || '').toLowerCase();
  const platform = [
    navigator.userAgentData?.platform,
    navigator.platform
  ].filter(Boolean).join(' ').toLowerCase();

  if (ua.includes('android') || platform.includes('android')) return 'android';
  if (platform.includes('linux') || ua.includes('linux')) return 'linux';
  if (platform.includes('win') || ua.includes('windows')) return 'windows';
  if (platform.includes('mac') || ua.includes('mac os')) return 'macos';
  return 'unknown';
}

function getClientOsLabel(os) {
  return CLIENT_OS_LABELS[os] || CLIENT_OS_LABELS.unknown;
}

function parseUrlLike(value) {
  const text = normalizeUrl(value);
  if (!text) return null;

  const urlText = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(text)
    ? text
    : `http://${text}`;

  try {
    return new URL(urlText);
  } catch {
    return null;
  }
}

function isLoopbackHostname(hostname) {
  const host = String(hostname || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
  return Boolean(
    host === 'localhost'
    || host.endsWith('.localhost')
    || host === '::1'
    || host === '0.0.0.0'
    || /^127(?:\.\d{1,3}){0,3}$/.test(host)
  );
}

function isLoopbackAgentEndpoint(agentBaseUrl) {
  const parsed = parseUrlLike(agentBaseUrl);
  return !parsed?.hostname || isLoopbackHostname(parsed.hostname);
}

function resolveTargetOpenUrl(openUrl, agentBaseUrl, isRemoteAgentEndpoint) {
  if (!isRemoteAgentEndpoint) return openUrl;

  const parsedOpenUrl = parseUrlLike(openUrl);
  const parsedAgentUrl = parseUrlLike(agentBaseUrl);
  if (!parsedOpenUrl || !parsedAgentUrl || !isLoopbackHostname(parsedOpenUrl.hostname)) {
    return openUrl;
  }

  parsedOpenUrl.hostname = parsedAgentUrl.hostname;
  return parsedOpenUrl.toString();
}

function App() {
  const [settings, setSettings] = useState(readSettings);
  const [draftSettings, setDraftSettings] = useState(settings);
  const [apps, setApps] = useState([]);
  const [installedApps, setInstalledApps] = useState([]);
  const [installedAppsReady, setInstalledAppsReady] = useState(false);
  const [latestAgentPackage, setLatestAgentPackage] = useState(null);
  const [agentHealth, setAgentHealth] = useState(null);
  const [systemInfo, setSystemInfo] = useState(null);
  const [packageStatus, setPackageStatus] = useState({ state: 'idle', message: '' });
  const [agentStatus, setAgentStatus] = useState({ state: 'idle', message: '' });
  const [selectedAppId, setSelectedAppId] = useState('');
  const [selectedDetail, setSelectedDetail] = useState(null);
  const [selectedManifest, setSelectedManifest] = useState(null);
  const [detailStatus, setDetailStatus] = useState({ state: 'idle', message: '' });
  const [query, setQuery] = useState('');
  const [filter, setFilter] = useState('all');
  const [toast, setToast] = useState(null);
  const [busyAction, setBusyAction] = useState('');
  const [activeTask, setActiveTask] = useState(null);
  const [endpointDialogOpen, setEndpointDialogOpen] = useState(false);
  const [agentDialogOpen, setAgentDialogOpen] = useState(false);
  const agentEndpointRef = useRef(settings.agentBaseUrl);
  const agentEndpointGenerationRef = useRef(0);
  const agentRefreshGenerationRef = useRef(0);

  const packageBaseUrl = settings.packageBaseUrl;
  const agentBaseUrl = settings.agentBaseUrl;
  agentEndpointRef.current = agentBaseUrl;
  const installCommand = `curl -fsSL ${joinUrl(packageBaseUrl, '/install-agent.sh')} | sudo bash`;
  const agentCommand = latestAgentPackage?.installCommand || installCommand;
  const clientOs = useMemo(() => detectClientOs(), []);
  const isClientLinux = clientOs === 'linux';
  const isClientWindows = clientOs === 'windows';
  const isLocalAgentEndpoint = useMemo(() => isLoopbackAgentEndpoint(agentBaseUrl), [agentBaseUrl]);
  const isRemoteAgentEndpoint = !isLocalAgentEndpoint;
  const canUseAgentEndpoint = isRemoteAgentEndpoint || isClientLinux;
  const canManageApps = canUseAgentEndpoint;
  const canShowAgentCommand = isClientLinux || isClientWindows || isRemoteAgentEndpoint;
  const clientOsLabel = getClientOsLabel(clientOs);
  const agentTargetLabel = isRemoteAgentEndpoint ? 'Remote Ubuntu Agent' : 'Local Agent';
  const agentTargetMachine = isRemoteAgentEndpoint ? 'remote Ubuntu server' : 'local machine';

  const installedByAppId = useMemo(() => {
    return new Map(installedApps.map((app) => [app.appId, app]));
  }, [installedApps]);

  const mergedApps = useMemo(() => {
    return apps.map((app) => {
      const installed = installedByAppId.get(app.appId) || installedByAppId.get(app.appCode);
      const isInstalled = Boolean(installed);
      const canUpdate = Boolean(isInstalled && installed.version && installed.version !== app.version);
      const needsAttention = Boolean(isInstalled && installed.status === 'attention');

      return {
        ...app,
        installed,
        localStatus: canUpdate ? 'update' : (isInstalled ? 'installed' : 'available'),
        canUpdate,
        needsAttention
      };
    });
  }, [apps, installedByAppId]);

  const filteredApps = useMemo(() => {
    const needle = query.trim().toLowerCase();
    return mergedApps.filter((app) => {
      const matchesQuery = !needle || [
        app.appId,
        app.appCode,
        app.appName,
        app.version,
        app.status
      ].join(' ').toLowerCase().includes(needle);

      if (!matchesQuery) return false;
      if (filter === 'installed') return app.localStatus === 'installed' || app.localStatus === 'update';
      if (filter === 'updates') return app.localStatus === 'update';
      if (filter === 'available') return app.localStatus === 'available';
      return true;
    });
  }, [filter, mergedApps, query]);

  const selectedApp = useMemo(() => {
    return mergedApps.find((app) => app.appId === selectedAppId) || mergedApps[0] || null;
  }, [mergedApps, selectedAppId]);

  const agentNeedsUpdate = useMemo(() => {
    return Boolean(
      agentHealth?.agentVersion
      && latestAgentPackage?.version
      && compareAgentVersions(agentHealth.agentVersion, latestAgentPackage.version) < 0
    );
  }, [agentHealth?.agentVersion, latestAgentPackage?.version]);

  const notify = useCallback((type, message) => {
    setToast({ id: Date.now(), type, message });
  }, []);

  const refreshPackage = useCallback(async () => {
    setPackageStatus({ state: 'loading', message: 'Đang tải app từ package server' });
    try {
      const [nextApps, nextAgentPackage] = await Promise.all([
        fetchPackageApps(packageBaseUrl),
        fetchLatestAgentPackage(packageBaseUrl).catch(() => null)
      ]);
      setApps(nextApps);
      setLatestAgentPackage(nextAgentPackage);
      const agentNote = nextAgentPackage?.version ? ` · Agent ${nextAgentPackage.version}` : '';
      setPackageStatus({ state: 'success', message: `${nextApps.length} app released${agentNote}` });
      return nextApps;
    } catch (error) {
      setPackageStatus({ state: 'danger', message: getErrorMessage(error) });
      setApps([]);
      setLatestAgentPackage(null);
      return [];
    }
  }, [packageBaseUrl]);

  const refreshAgent = useCallback(async () => {
    const requestEndpoint = agentBaseUrl;
    if (agentEndpointRef.current !== requestEndpoint) return false;

    const requestGeneration = ++agentRefreshGenerationRef.current;
    const isCurrentRequest = () => (
      agentEndpointRef.current === requestEndpoint
      && agentRefreshGenerationRef.current === requestGeneration
    );

    if (!canUseAgentEndpoint) {
      if (!isCurrentRequest()) return false;
      setAgentHealth(null);
      setSystemInfo(null);
      setInstalledApps([]);
      setInstalledAppsReady(false);
      setAgentStatus({
        state: 'warning',
        message: isClientWindows
          ? 'Local endpoint points to this Windows machine. Use a remote Ubuntu Agent endpoint.'
          : `Local endpoint requires Linux. Current client is ${clientOsLabel}.`
      });
      return false;
    }

    setAgentStatus({ state: 'loading', message: `Checking ${agentTargetLabel}` });
    setInstalledAppsReady(false);
    try {
      const health = await fetchAgentHealth(requestEndpoint);
      if (!isCurrentRequest()) return false;
      setAgentHealth(health);
      setAgentStatus({ state: 'success', message: `${health.hostname || agentTargetLabel} online` });

      const [infoResult, installedResult] = await Promise.allSettled([
        fetchAgentSystemInfo(requestEndpoint),
        fetchInstalledApps(requestEndpoint)
      ]);
      if (!isCurrentRequest()) return false;
      setSystemInfo(infoResult.status === 'fulfilled' ? infoResult.value : null);
      if (installedResult.status === 'fulfilled') {
        setInstalledApps(installedResult.value);
        setInstalledAppsReady(true);
      } else {
        setInstalledApps([]);
        setInstalledAppsReady(false);
        setAgentStatus({
          state: 'warning',
          message: `${health.hostname || agentTargetLabel} online · installed app status unavailable: ${getErrorMessage(installedResult.reason)}`
        });
      }
      return true;
    } catch (error) {
      if (!isCurrentRequest()) return false;
      setAgentHealth(null);
      setSystemInfo(null);
      setInstalledApps([]);
      setInstalledAppsReady(false);
      setAgentStatus({ state: 'danger', message: getErrorMessage(error) });
      return false;
    }
  }, [agentBaseUrl, agentTargetLabel, canUseAgentEndpoint, clientOsLabel, isClientWindows]);

  const refreshAll = useCallback(async () => {
    await Promise.all([refreshPackage(), refreshAgent()]);
  }, [refreshAgent, refreshPackage]);

  const loadSelectedDetail = useCallback(async (app) => {
    if (!app) {
      setSelectedDetail(null);
      setSelectedManifest(null);
      return;
    }

    setDetailStatus({ state: 'loading', message: 'Đang tải manifest' });
    try {
      const [detail, manifest] = await Promise.all([
        fetchApplicationDetail(packageBaseUrl, app.appId).catch(() => null),
        fetchApplicationManifest(packageBaseUrl, app.appId, app.version).catch(() => null)
      ]);
      setSelectedDetail(detail);
      setSelectedManifest(manifest);
      setDetailStatus({ state: 'success', message: manifest ? 'Manifest sẵn sàng' : 'Đã tải app detail' });
    } catch (error) {
      setSelectedDetail(null);
      setSelectedManifest(null);
      setDetailStatus({ state: 'danger', message: getErrorMessage(error) });
    }
  }, [packageBaseUrl]);

  const loadTaskSnapshot = useCallback(async (taskId) => {
    try {
      const [nextTask, logs, components] = await Promise.all([
        fetchTaskStatus(agentBaseUrl, taskId),
        fetchTaskLogs(agentBaseUrl, taskId).catch(() => null),
        fetchTaskComponents(agentBaseUrl, taskId).catch(() => null)
      ]);
      const snapshot = {
        ...nextTask,
        ...(logs ? { logs } : {}),
        ...(components ? { components } : {}),
        pollError: ''
      };

      setActiveTask((current) => {
        if (!current || current.taskId !== taskId) return current;
        return { ...current, ...snapshot };
      });

      if (TERMINAL_TASK_STATUSES.has(snapshot.status)) {
        await refreshAgent();
      }
      return snapshot;
    } catch (error) {
      const message = getErrorMessage(error);
      setActiveTask((current) => {
        if (!current || current.taskId !== taskId) return current;
        return { ...current, pollError: message };
      });
      return null;
    }
  }, [agentBaseUrl, refreshAgent]);

  const startTask = useCallback((queuedTask, action, app) => {
    const queuedAt = new Date().toISOString();
    setActiveTask({
      taskId: queuedTask.taskId,
      action,
      appId: app.appId,
      appName: app.appName,
      status: queuedTask.status || 'queued',
      progress: 0,
      currentStep: 'queued',
      logs: [
        {
          time: queuedAt,
          level: 'info',
          message: `Task ${queuedTask.taskId} queued`
        }
      ],
      components: [],
      pollError: '',
      queuedAt
    });
  }, []);

  const startPreflightFailedTask = useCallback((action, app, message) => {
    const failedAt = new Date().toISOString();
    const taskId = `preflight_${Date.now()}`;

    setActiveTask({
      taskId,
      action,
      appId: app.appId,
      appName: app.appName,
      status: 'failed',
      progress: 5,
      currentStep: 'package preflight failed',
      errorMessage: message,
      logs: [
        {
          time: failedAt,
          level: 'error',
          message
        }
      ],
      components: [],
      pollError: '',
      localOnly: true,
      queuedAt: failedAt,
      finishedAt: failedAt
    });
  }, []);

  const runAppAction = useCallback(async (action, app) => {
    if (!canManageApps) {
      notify('warning', 'Local Agent endpoint can install only from a Linux browser machine. Use a remote Ubuntu Agent endpoint.');
      return;
    }

    if (!agentHealth) {
      notify('warning', `${agentTargetLabel} is offline. Check the endpoint and press Retry.`);
      return;
    }

    if (!installedAppsReady) {
      notify('warning', 'Installed app status is not ready. Refresh the Agent before running an action.');
      return;
    }

    if (action === 'remove' && !window.confirm(`Remove and clean ${app.appName} from ${agentTargetMachine}?`)) {
      return;
    }

    const actionEndpoint = agentBaseUrl;
    const endpointGeneration = agentEndpointGenerationRef.current;
    const isCurrentEndpoint = () => (
      agentEndpointRef.current === actionEndpoint
      && agentEndpointGenerationRef.current === endpointGeneration
    );
    if (!isCurrentEndpoint()) {
      notify('warning', 'Agent endpoint changed. Retry the action on the current Agent.');
      return;
    }

    const key = `${action}:${app.appId}`;
    setBusyAction(key);
    try {
      let queuedTask;
      if (action === 'install') {
        const manifest = await fetchApplicationManifest(packageBaseUrl, app.appId, app.version);
        if (!isCurrentEndpoint()) return;
        setSelectedManifest(manifest);
        setDetailStatus({ state: 'success', message: 'Manifest ready' });
        queuedTask = await queueInstall(actionEndpoint, app);
      } else if (action === 'update') {
        const manifest = await fetchApplicationManifest(packageBaseUrl, app.appId, app.version);
        if (!isCurrentEndpoint()) return;
        setSelectedManifest(manifest);
        setDetailStatus({ state: 'success', message: 'Manifest ready' });
        queuedTask = await queueUpdate(actionEndpoint, app, app.installed);
      } else {
        queuedTask = await queueRemove(actionEndpoint, app);
      }

      if (!isCurrentEndpoint()) {
        notify('warning', 'Agent endpoint changed while the request was running. Switch back to monitor the previous Agent.');
        return;
      }
      startTask(queuedTask, action, app);
      notify('success', `Đã queue task ${queuedTask.taskId}`);
    } catch (error) {
      if (!isCurrentEndpoint()) return;
      const message = getErrorMessage(error);
      if (action === 'install' || action === 'update') {
        setSelectedManifest(null);
        setDetailStatus({ state: 'danger', message });
        startPreflightFailedTask(action, app, message);
      }
      notify('failure', message);
    } finally {
      setBusyAction((current) => current === key ? '' : current);
    }
  }, [agentBaseUrl, agentHealth, agentTargetLabel, agentTargetMachine, canManageApps, installedAppsReady, notify, packageBaseUrl, startPreflightFailedTask, startTask]);

  const openEndpointDialog = useCallback(() => {
    setDraftSettings(settings);
    setEndpointDialogOpen(true);
  }, [settings]);

  const closeEndpointDialog = useCallback(() => {
    setDraftSettings(settings);
    setEndpointDialogOpen(false);
  }, [settings]);

  const applySettings = useCallback(() => {
    const nextSettings = {
      packageBaseUrl: normalizeUrl(draftSettings.packageBaseUrl || DEFAULT_PACKAGE_BASE_URL),
      agentBaseUrl: normalizeUrl(draftSettings.agentBaseUrl || DEFAULT_AGENT_BASE_URL)
    };
    const endpointsChanged = (
      nextSettings.packageBaseUrl !== settings.packageBaseUrl
      || nextSettings.agentBaseUrl !== settings.agentBaseUrl
    );
    if (busyAction && endpointsChanged) {
      notify('warning', 'Wait for the current Agent request to finish before changing endpoints.');
      return;
    }
    if (nextSettings.agentBaseUrl !== settings.agentBaseUrl) {
      agentEndpointRef.current = nextSettings.agentBaseUrl;
      agentEndpointGenerationRef.current += 1;
      agentRefreshGenerationRef.current += 1;
      setAgentHealth(null);
      setSystemInfo(null);
      setInstalledApps([]);
      setInstalledAppsReady(false);
      setActiveTask(null);
    }
    setSettings(nextSettings);
    setDraftSettings(nextSettings);
    saveSettings(nextSettings);
    setEndpointDialogOpen(false);
    notify('info', 'Đã cập nhật endpoint test');
  }, [busyAction, draftSettings, notify, settings.agentBaseUrl, settings.packageBaseUrl]);

  const copyInstallCommand = useCallback(async () => {
    if (!canShowAgentCommand) {
      notify('warning', 'Agent command is hidden for this client OS.');
      return;
    }

    if (!navigator.clipboard?.writeText) {
      if (copyTextFallback(agentCommand)) {
        notify('success', agentNeedsUpdate ? 'Da copy lenh update Agent' : 'Da copy lenh cai Agent');
        return;
      }

      window.prompt('Copy Agent command', agentCommand);
      notify('warning', 'Browser dang chan copy tu dong. Hay copy lenh trong popup.');
      return;
    }

    try {
      await navigator.clipboard.writeText(agentCommand);
      notify('success', agentNeedsUpdate ? 'Đã copy lệnh update Agent' : 'Đã copy lệnh cài Agent');
    } catch {
      notify('warning', 'Không thể copy tự động trong browser này');
    }
  }, [agentCommand, agentNeedsUpdate, canShowAgentCommand, notify]);

  useEffect(() => {
    refreshAll();
  }, [refreshAll]);

  useEffect(() => {
    if (!selectedApp) {
      setSelectedDetail(null);
      setSelectedManifest(null);
      return;
    }
    setSelectedAppId(selectedApp.appId);
    loadSelectedDetail(selectedApp);
  }, [loadSelectedDetail, selectedApp?.appId]);

  useEffect(() => {
    if (!activeTask?.taskId || TERMINAL_TASK_STATUSES.has(activeTask.status)) return undefined;
    let disposed = false;
    let terminalNotified = false;

    async function poll() {
      const nextTask = await loadTaskSnapshot(activeTask.taskId);
      if (disposed || !nextTask) return;
      if (TERMINAL_TASK_STATUSES.has(nextTask.status)) {
        window.clearInterval(timer);
        if (!terminalNotified) {
          terminalNotified = true;
          if (nextTask.status === 'success') {
            notify('success', `${activeTask.appName || 'Task'} completed`);
          } else if (nextTask.status === 'failed') {
            notify('failure', nextTask.errorMessage || `Task ${nextTask.taskId} failed`);
          } else {
            notify('warning', `Task ${nextTask.taskId} ${nextTask.status}`);
          }
        }
      }
    }

    poll();
    const timer = window.setInterval(poll, 1200);

    return () => {
      disposed = true;
      window.clearInterval(timer);
    };
  }, [activeTask?.action, activeTask?.appName, activeTask?.status, activeTask?.taskId, loadTaskSnapshot, notify]);

  useEffect(() => {
    if (!toast) return undefined;
    const timer = window.setTimeout(() => setToast(null), 3200);
    return () => window.clearTimeout(timer);
  }, [toast]);

  useEffect(() => {
    if (!endpointDialogOpen && !agentDialogOpen) return undefined;

    function onKeyDown(event) {
      if (event.key === 'Escape') {
        if (endpointDialogOpen) closeEndpointDialog();
        if (agentDialogOpen) setAgentDialogOpen(false);
      }
    }

    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [agentDialogOpen, closeEndpointDialog, endpointDialogOpen]);

  const agentStatusTitle = !canUseAgentEndpoint
    ? (isClientWindows ? 'Remote endpoint needed' : 'Linux client required')
    : (agentNeedsUpdate ? 'Agent update available' : (agentHealth ? 'Agent online' : 'Agent offline'));
  const agentStatusDetail = !canUseAgentEndpoint
    ? (isClientWindows ? 'Change endpoint to Ubuntu server IP' : `${clientOsLabel} detected`)
    : (agentHealth ? `${agentHealth.hostname || agentTargetLabel} · ${agentHealth.agentVersion || '-'}${agentNeedsUpdate ? ` -> ${latestAgentPackage.version}` : ''}` : agentBaseUrl);
  const agentStatusTone = !canUseAgentEndpoint
    ? 'warning'
    : (agentNeedsUpdate ? 'warning' : (agentHealth ? 'success' : (agentStatus.state === 'loading' ? 'info' : 'danger')));
  const topbarAgentState = !canUseAgentEndpoint
    ? 'Use Ubuntu Agent endpoint'
    : (agentHealth ? `Ready on ${agentTargetMachine}` : `Waiting for ${agentTargetLabel}`);

  return (
    <div className="app-shell">
      <aside className="sidebar">
        <div className="brand-block">
          <div className="brand-mark">
            <PackageCheck fontSize={20} aria-hidden="true" />
          </div>
          <div className="brand-copy">
            <strong>Robot Installer</strong>
            <span>Web Client</span>
          </div>
        </div>

        <div className="nav-section">
          <div className="nav-label">Runtime</div>
          <StatusBox
            icon={agentHealth ? PlugZap : WifiOff}
            title={agentStatusTitle}
            detail={agentStatusDetail}
            tone={agentStatusTone}
          />
          <StatusBox
            icon={Server}
            title="Package API"
            detail={packageStatus.message || packageBaseUrl}
            tone={packageStatus.state === 'success' ? 'success' : packageStatus.state}
          />

          <div className="nav-label">Endpoint</div>
          <div className="endpoint-summary" aria-label="Current endpoints">
            <div className="endpoint-summary-row">
              <span>Package server</span>
              <strong title={packageBaseUrl}>{packageBaseUrl}</strong>
            </div>
            <div className="endpoint-summary-row">
              <span>Agent endpoint</span>
              <strong title={agentBaseUrl}>{agentBaseUrl}</strong>
            </div>
          </div>
          <button className="btn btn-secondary full" type="button" onClick={openEndpointDialog}>
            <Settings fontSize={15} aria-hidden="true" />
            Endpoint settings
          </button>
        </div>
      </aside>

      <main className="main-shell">
        <header className="topbar">
          <div className="topbar-title">
            <span>robot.installer</span>
            <strong>{topbarAgentState}</strong>
          </div>
          <div className="topbar-actions">
            <AgentStatusButton
              detail={agentStatusDetail}
              onClick={() => setAgentDialogOpen(true)}
              title={agentStatusTitle}
              tone={agentStatusTone}
            />
            <a className="icon-button" href={joinUrl(packageBaseUrl, '/api/apps')} target="_blank" rel="noreferrer" title="Open package API">
              <ExternalLink fontSize={17} aria-hidden="true" />
            </a>
            <button className="btn btn-secondary" type="button" onClick={refreshAll}>
              <RefreshCcw fontSize={15} aria-hidden="true" />
              Retry
            </button>
          </div>
        </header>

        <section className="page">
          {!canUseAgentEndpoint && (
            <ClientOsNotice
              agentCommand={agentCommand}
              canShowCommand={canShowAgentCommand}
              isWindows={isClientWindows}
              onCopyCommand={copyInstallCommand}
              osLabel={clientOsLabel}
            />
          )}

          {canUseAgentEndpoint && !agentHealth && (
            <div className="offline-banner">
              <AlertCircle fontSize={19} aria-hidden="true" />
              <div>
                <strong>{agentTargetLabel} is offline</strong>
                <code>{agentCommand}</code>
              </div>
              <button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
                <Clipboard fontSize={15} aria-hidden="true" />
                Copy
              </button>
            </div>
          )}

          {canUseAgentEndpoint && agentNeedsUpdate && (
            <div className="offline-banner agent-update-banner">
              <AlertCircle fontSize={19} aria-hidden="true" />
              <div>
                <strong>Agent {latestAgentPackage.version} is ready</strong>
                <code>{agentCommand}</code>
              </div>
              <button className="btn btn-secondary" type="button" onClick={copyInstallCommand}>
                <Clipboard fontSize={15} aria-hidden="true" />
                Copy
              </button>
            </div>
          )}

          <div className="workbench-grid">
            <section className="table-panel">
              <div className="page-filters inline">
                <label className="filter-field wide">
                  <span>Search</span>
                  <div className="input-with-icon">
                    <Search fontSize={15} aria-hidden="true" />
                    <input
                      value={query}
                      onChange={(event) => setQuery(event.target.value)}
                      placeholder="App code, name, version..."
                    />
                  </div>
                </label>
                <label className="filter-field">
                  <span>Status</span>
                  <select value={filter} onChange={(event) => setFilter(event.target.value)}>
                    <option value="all">All</option>
                    <option value="available">Available</option>
                    <option value="installed">Installed</option>
                    <option value="updates">Updates</option>
                  </select>
                </label>
              </div>

              <div className="table-wrap">
                <table>
                  <thead>
                    <tr>
                      <th>Application</th>
                      <th>Released</th>
                      <th>Local</th>
                      <th>Packages</th>
                      <th className="action-col">Actions</th>
                    </tr>
                  </thead>
                  <tbody>
                    {packageStatus.state === 'loading' && (
                      <tr>
                        <td colSpan="5" className="table-empty">Đang tải danh sách app...</td>
                      </tr>
                    )}
                    {packageStatus.state === 'danger' && (
                      <tr>
                        <td colSpan="5" className="table-empty danger-text">{packageStatus.message}</td>
                      </tr>
                    )}
                    {packageStatus.state !== 'loading' && packageStatus.state !== 'danger' && filteredApps.length === 0 && (
                      <tr>
                        <td colSpan="5" className="table-empty">Chưa có app phù hợp bộ lọc.</td>
                      </tr>
                    )}
                    {filteredApps.map((app) => {
                      const rowTask = activeTask?.appId === app.appId ? activeTask : null;
                      const rowTaskBusy = isTaskActive(rowTask);
                      const installBusy = busyAction === `install:${app.appId}` || (rowTaskBusy && rowTask.action === 'install');
                      const updateBusy = busyAction === `update:${app.appId}` || (rowTaskBusy && rowTask.action === 'update');
                      const removeBusy = busyAction === `remove:${app.appId}` || (rowTaskBusy && rowTask.action === 'remove');
                      const appOpenUrl = app.installed
                        ? getAppOpenUrl({ ...app, openUrl: app.openUrl || app.installed.openUrl })
                        : '';
                      const openUrl = resolveTargetOpenUrl(appOpenUrl, agentBaseUrl, isRemoteAgentEndpoint);

                      return (
                        <tr
                          key={app.appId}
                          className={selectedApp?.appId === app.appId ? 'selected-row' : ''}
                          onClick={() => setSelectedAppId(app.appId)}
                        >
                          <td>
                            <button className="table-title as-button" type="button" onClick={() => setSelectedAppId(app.appId)}>
                              {app.appName}
                            </button>
                            <span className="table-subtitle">{app.appCode || app.appId}</span>
                          </td>
                          <td>
                            <span className="badge badge-info">{app.version}</span>
                          </td>
                          <td>
                            <LocalStatus app={app} task={rowTaskBusy ? rowTask : null} />
                          </td>
                          <td>{app.packageCount || 0}</td>
                          <td className="action-col">
                            <div className="action-group">
                              {canManageApps && !app.installed && (
                                <button
                                  className="btn btn-primary compact"
                                  type="button"
                                  disabled={!agentHealth || !installedAppsReady || rowTaskBusy || installBusy}
                                  onClick={(event) => {
                                    event.stopPropagation();
                                    runAppAction('install', app);
                                  }}
                                >
                                  {installBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <Play fontSize={14} aria-hidden="true" />}
                                  Install
                                </button>
                              )}
                              {canManageApps && app.installed && app.needsAttention && (
                                <button
                                  className="btn btn-warning compact"
                                  type="button"
                                  disabled={!agentHealth || !installedAppsReady || rowTaskBusy || updateBusy}
                                  onClick={(event) => {
                                    event.stopPropagation();
                                    runAppAction('update', app);
                                  }}
                                >
                                  {updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
                                  Retry
                                </button>
                              )}
                              {canManageApps && app.installed && app.canUpdate && !app.needsAttention && (
                                <button
                                  className="btn btn-warning compact"
                                  type="button"
                                  disabled={!agentHealth || !installedAppsReady || rowTaskBusy || updateBusy}
                                  onClick={(event) => {
                                    event.stopPropagation();
                                    runAppAction('update', app);
                                  }}
                                >
                                  {updateBusy ? <Loader2 className="spin" fontSize={14} aria-hidden="true" /> : <RotateCcw fontSize={14} aria-hidden="true" />}
                                  Update
                                </button>
                              )}
                              {canManageApps && app.installed && openUrl && (
                                <a
                                  className="btn btn-secondary compact"
                                  href={openUrl}
                                  target="_blank"
                                  rel="noreferrer"
                                  title={`Open ${app.appName}`}
                                  onClick={(event) => event.stopPropagation()}
                                >
                                  <ExternalLink fontSize={14} aria-hidden="true" />
                                  Open App
                                </a>
                              )}
                              {canManageApps && app.installed && (
                                <button
                                  className="icon-button danger"
                                  type="button"
                                  title="Remove"
                                  disabled={!agentHealth || !installedAppsReady || rowTaskBusy || removeBusy}
                                  onClick={(event) => {
                                    event.stopPropagation();
                                    runAppAction('remove', app);
                                  }}
                                >
                                  {removeBusy ? <Loader2 className="spin" fontSize={16} aria-hidden="true" /> : <Trash2 fontSize={16} aria-hidden="true" />}
                                </button>
                              )}
                            </div>
                          </td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
              <div className="page-pager">
                <span>{filteredApps.length} / {mergedApps.length} apps</span>
                <span>{packageBaseUrl}</span>
              </div>
            </section>

            <aside className="side-stack">
              {activeTask && (
                <TaskPanel
                  task={activeTask}
                  onClear={() => setActiveTask(null)}
                  onRefresh={() => loadTaskSnapshot(activeTask.taskId)}
                />
              )}
              <AppDetailPanel
                app={selectedApp}
                detail={selectedDetail}
                manifest={selectedManifest}
                status={detailStatus}
                packageBaseUrl={packageBaseUrl}
              />
            </aside>
          </div>
        </section>
      </main>

      {toast && <Toast toast={toast} />}
      {agentDialogOpen && (
        <AgentDialog
          endpoint={agentBaseUrl}
          health={agentHealth}
          latestAgentPackage={latestAgentPackage}
          needsUpdate={agentNeedsUpdate}
          onClose={() => setAgentDialogOpen(false)}
          onCopyUpdate={copyInstallCommand}
          showAgentActions={canShowAgentCommand}
          status={agentStatus}
          systemInfo={systemInfo}
          title={agentTargetLabel}
        />
      )}
      {endpointDialogOpen && (
        <EndpointDialog
          draftSettings={draftSettings}
          onApply={applySettings}
          onCancel={closeEndpointDialog}
          onChange={setDraftSettings}
        />
      )}
    </div>
  );
}

function AgentStatusButton({ title, detail, tone, onClick }) {
  const Icon = tone === 'success'
    ? CheckCircle2
    : tone === 'danger'
      ? XCircle
      : tone === 'warning'
        ? AlertCircle
        : Loader2;

  return (
    <button className={`agent-status-button tone-${tone || 'info'}`} type="button" onClick={onClick}>
      <span className="agent-status-icon">
        <Icon className={tone === 'info' ? 'spin' : ''} fontSize={16} aria-hidden="true" />
      </span>
      <span className="agent-status-copy">
        <strong>{title}</strong>
        <small>{detail}</small>
      </span>
    </button>
  );
}

function AgentDialog(props) {
  return (
    <div className="dialog-backdrop">
      <section
        aria-labelledby="agent-dialog-title"
        aria-modal="true"
        className="dialog-panel agent-dialog-panel"
        role="dialog"
      >
        <AgentPanel {...props} />
      </section>
    </div>
  );
}

function EndpointDialog({ draftSettings, onApply, onCancel, onChange }) {
  return (
    <div className="dialog-backdrop">
      <section
        aria-labelledby="endpoint-dialog-title"
        aria-modal="true"
        className="dialog-panel"
        role="dialog"
      >
        <form
          onSubmit={(event) => {
            event.preventDefault();
            onApply();
          }}
        >
          <div className="dialog-header">
            <div>
              <h2 id="endpoint-dialog-title">Endpoint settings</h2>
              <p>Changes only apply after you press Apply.</p>
            </div>
            <button className="icon-button subtle" type="button" title="Close" onClick={onCancel}>
              <X fontSize={16} aria-hidden="true" />
            </button>
          </div>

          <div className="dialog-body">
            <label className="settings-field">
              <span>Package server</span>
              <input
                autoFocus
                value={draftSettings.packageBaseUrl}
                onChange={(event) => onChange((current) => ({
                  ...current,
                  packageBaseUrl: event.target.value
                }))}
              />
            </label>
            <label className="settings-field">
              <span>Agent endpoint</span>
              <input
                value={draftSettings.agentBaseUrl}
                onChange={(event) => onChange((current) => ({
                  ...current,
                  agentBaseUrl: event.target.value
                }))}
              />
            </label>
          </div>

          <div className="dialog-actions">
            <button className="btn btn-secondary" type="button" onClick={onCancel}>
              Cancel
            </button>
            <button className="btn btn-primary" type="submit">
              <Settings fontSize={15} aria-hidden="true" />
              Apply endpoints
            </button>
          </div>
        </form>
      </section>
    </div>
  );
}

function StatusBox({ icon: Icon, title, detail, tone }) {
  return (
    <div className={`status-box tone-${tone || 'muted'}`}>
      <span className="status-icon"><Icon fontSize={16} aria-hidden="true" /></span>
      <div>
        <strong>{title}</strong>
        <span>{detail}</span>
      </div>
    </div>
  );
}

function ClientOsNotice({ agentCommand, canShowCommand, isWindows, onCopyCommand, osLabel }) {
  return (
    <div className={`offline-banner client-os-banner ${canShowCommand ? 'command-visible' : ''}`}>
      <AlertCircle fontSize={19} aria-hidden="true" />
      <div>
        <strong>{isWindows ? 'Remote Ubuntu endpoint needed' : 'Local Agent requires Linux'}</strong>
        <p>
          {isWindows
            ? 'The endpoint is localhost, so it points at this Windows machine. Change it to http://<ubuntu-server-ip>:5010 or run the command below over Ubuntu SSH.'
            : `Current client is ${osLabel}. Use a remote Ubuntu Agent endpoint or open the Web Client from Linux.`}
        </p>
        {canShowCommand && <code>{agentCommand}</code>}
      </div>
      {canShowCommand && (
        <button className="btn btn-secondary" type="button" onClick={onCopyCommand}>
          <Clipboard fontSize={15} aria-hidden="true" />
          Copy
        </button>
      )}
    </div>
  );
}

function LocalStatus({ app, task }) {
  if (isTaskActive(task)) {
    return (
      <span className="status-inline">
        <span className={statusBadgeClass(task.status)}>
          {TASK_ACTION_LABELS[task.action] || task.status}
        </span>
        <small>{clampProgress(task.progress)}%</small>
      </span>
    );
  }

  if (app.installed) {
    const serviceSummary = serviceCheckSummary(
      app.installed.serviceCheckStatus,
      app.installed.serviceChecks
    );
    const statusLabel = app.needsAttention
      ? 'Attention'
      : app.localStatus === 'update'
        ? 'Update'
        : 'Installed';
    const statusTone = app.needsAttention || app.localStatus === 'update' ? 'warning' : 'success';
    return (
      <span className="status-stack">
        <span className="status-inline">
          <span className={`badge badge-${statusTone}`}>{statusLabel}</span>
          <small>{app.installed.version}</small>
        </span>
        {serviceSummary && (
          <small className={app.installed.serviceCheckStatus === 'unhealthy' ? 'danger-text' : 'success-text'}>
            {serviceSummary}
          </small>
        )}
      </span>
    );
  }

  return <span className="badge badge-muted">Available</span>;
}

function TaskPanel({ task, onClear, onRefresh }) {
  const progress = clampProgress(task.progress);
  const statusTone = TASK_STATUS_TONES[task.status] || 'info';
  const components = task.components || [];
  const logs = task.logs || [];
  const visibleLogs = logs.slice(-6);
  const canClear = TERMINAL_TASK_STATUSES.has(task.status);
  const canRefresh = !task.localOnly;

  return (
    <section className="panel task-panel">
      <div className="panel-header">
        <div>
          <h2>Task monitor</h2>
          <p>{task.taskId}</p>
        </div>
        <div className="panel-actions">
          <span className={statusBadgeClass(task.status)}>{task.status || 'queued'}</span>
          <button className="icon-button subtle" type="button" title="Refresh task" onClick={onRefresh} disabled={!canRefresh}>
            <RefreshCcw fontSize={16} aria-hidden="true" />
          </button>
          {canClear && (
            <button className="icon-button subtle" type="button" title="Clear task" onClick={onClear}>
              <XCircle fontSize={16} aria-hidden="true" />
            </button>
          )}
        </div>
      </div>

      <div className="task-progress" aria-label={`Task progress ${progress}%`}>
        <div className={`task-progress-bar tone-${statusTone}`} style={{ width: `${progress}%` }} />
      </div>

      <dl className="detail-list compact-list">
        <div>
          <dt>Action</dt>
          <dd>{task.action || task.type || '-'}</dd>
        </div>
        <div>
          <dt>App</dt>
          <dd>{task.appName || task.appId || '-'}</dd>
        </div>
        <div>
          <dt>Step</dt>
          <dd>{task.currentStep || '-'}</dd>
        </div>
        {(task.errorMessage || task.pollError) && (
          <div>
            <dt>Error</dt>
            <dd className="danger-text">{task.errorMessage || task.pollError}</dd>
          </div>
        )}
      </dl>

      <div className="component-list task-components">
        <div className="component-list-title">
          <Activity fontSize={15} aria-hidden="true" />
          Components
        </div>
        {components.map((component) => (
          <div className="component-item task-component-item" key={component.componentId}>
            <div>
              <strong>{component.componentId}</strong>
              <span>{component.currentStep || component.type || '-'}</span>
            </div>
            <span className={statusBadgeClass(component.status)}>{component.progress}%</span>
            {component.type === 'apt' && (
              <ServiceChecks
                checks={component.serviceChecks}
                status={component.serviceCheckStatus}
              />
            )}
          </div>
        ))}
        {!components.length && (
          <div className="table-empty compact-empty">Waiting for component details...</div>
        )}
      </div>

      <div className="task-log-list">
        {visibleLogs.map((log, index) => (
          <div className="task-log-line" key={`${log.time}-${log.level}-${index}`}>
            <span>{formatTaskTime(log.time)}</span>
            <strong>{log.level}</strong>
            <p>{log.message}</p>
          </div>
        ))}
        {!visibleLogs.length && (
          <div className="table-empty compact-empty">Waiting for task logs...</div>
        )}
      </div>
    </section>
  );
}

function ServiceChecks({ checks = [], status = 'not-checked', showSource = false }) {
  if (!checks.length) {
    const message = serviceCheckSummary(status, checks);
    if (!message && status === 'not-checked') return null;
    return (
      <div className="service-check-list">
        <div className="service-check-empty">
          <Activity fontSize={14} aria-hidden="true" />
          <span>{message || 'Service status unavailable'}</span>
        </div>
      </div>
    );
  }

  return (
    <div className="service-check-list" aria-label="Automatic service checks">
      {checks.map((check) => (
        <div className="service-check-row" key={`${check.componentId || ''}:${check.serviceName}`}>
          <div className="service-check-name">
            {check.healthy
              ? <CheckCircle2 className="service-check-icon healthy" fontSize={14} aria-hidden="true" />
              : <AlertCircle className="service-check-icon unhealthy" fontSize={14} aria-hidden="true" />}
            <span>
              <strong>{check.serviceName}</strong>
              <small>
                {showSource && (check.packageName || check.componentId)
                  ? `${check.packageName || check.componentId} · `
                  : ''}
                {check.subState || check.activeState || 'unknown'} · {check.unitFileState || 'unknown'}
                {check.checkedAt ? ` · ${check.stale ? 'last checked' : 'checked'} ${formatTaskTime(check.checkedAt)}` : ''}
              </small>
            </span>
          </div>
          <span className={`badge badge-${serviceCheckTone(check)}`}>{serviceCheckLabel(check)}</span>
        </div>
      ))}
    </div>
  );
}

function AgentPanel({ health, systemInfo, status, title, endpoint, latestAgentPackage, needsUpdate, onClose, onCopyUpdate, showAgentActions }) {
  const statusTone = needsUpdate
    ? 'warning'
    : health
      ? 'success'
      : status.state === 'loading'
        ? 'info'
        : status.state === 'warning'
          ? 'warning'
          : 'danger';
  const statusLabel = needsUpdate
    ? 'Update available'
    : health
      ? 'Online'
      : status.state === 'loading'
        ? 'Checking'
        : status.state === 'warning'
          ? 'Warning'
          : 'Offline';

  return (
    <section className="agent-detail">
      <div className="panel-header">
        <div>
          <h2 id="agent-dialog-title">{title || 'Agent'}</h2>
          <p>{status.message || endpoint || '127.0.0.1:5010'}</p>
        </div>
        <div className="panel-actions">
          {needsUpdate ? <AlertCircle className="panel-state warning" fontSize={20} aria-hidden="true" /> : health ? <CheckCircle2 className="panel-state success" fontSize={20} aria-hidden="true" /> : <XCircle className="panel-state danger" fontSize={20} aria-hidden="true" />}
          <button className="icon-button subtle" type="button" title="Close" onClick={onClose}>
            <X fontSize={16} aria-hidden="true" />
          </button>
        </div>
      </div>
      <dl className="detail-list compact-list">
        <div>
          <dt>Status</dt>
          <dd><span className={`badge badge-${statusTone}`}>{statusLabel}</span></dd>
        </div>
        <div>
          <dt>Endpoint</dt>
          <dd>{endpoint || '127.0.0.1:5010'}</dd>
        </div>
        <div>
          <dt>Version</dt>
          <dd>
            <span className={needsUpdate ? 'status-inline' : ''}>
              {health?.agentVersion || '-'}
              {needsUpdate && <span className="badge badge-warning">Update</span>}
            </span>
          </dd>
        </div>
        <div>
          <dt>Latest</dt>
          <dd>{latestAgentPackage?.version || '-'}</dd>
        </div>
        <div>
          <dt>Host</dt>
          <dd>{health?.hostname || '-'}</dd>
        </div>
        <div>
          <dt>OS</dt>
          <dd>{systemInfo?.os || health?.os || '-'}</dd>
        </div>
        <div>
          <dt>Arch</dt>
          <dd>{systemInfo?.architecture || health?.architecture || '-'}</dd>
        </div>
      </dl>
      <div className="agent-metrics">
        <span><Cpu fontSize={14} aria-hidden="true" /> {systemInfo?.kernel || 'kernel -'}</span>
        <span><HardDrive fontSize={14} aria-hidden="true" /> {systemInfo?.diskFree || 'disk -'}</span>
      </div>
      {showAgentActions && (
        <div className="agent-update-action">
          <button className={`btn ${needsUpdate ? 'btn-warning' : 'btn-secondary'}`} type="button" onClick={onCopyUpdate}>
            <Clipboard fontSize={15} aria-hidden="true" />
            {needsUpdate ? 'Copy update command' : 'Copy Agent command'}
          </button>
          {needsUpdate && latestAgentPackage?.downloadUrl && (
            <a className="btn btn-secondary" href={latestAgentPackage.downloadUrl} target="_blank" rel="noreferrer">
              <Download fontSize={15} aria-hidden="true" />
              Latest .deb
            </a>
          )}
        </div>
      )}
    </section>
  );
}

function AppDetailPanel({ app, detail, manifest, status, packageBaseUrl }) {
  const packages = detail?.packages || [];
  const components = manifest?.components || [];
  const installedServiceChecks = app?.installed?.serviceChecks || [];
  const installedServiceStatus = app?.installed?.serviceCheckStatus || 'not-checked';

  return (
    <section className="panel app-detail-panel">
      <div className="panel-header">
        <div>
          <h2>{app?.appName || 'App detail'}</h2>
          <p>{app?.appCode || app?.appId || status.message || packageBaseUrl}</p>
        </div>
        {status.state === 'loading' ? <Loader2 className="spin panel-state" fontSize={20} aria-hidden="true" /> : <Box className="panel-state" fontSize={20} aria-hidden="true" />}
      </div>

      {app ? (
        <>
          <dl className="detail-list compact-list">
            <div>
              <dt>Released</dt>
              <dd>{app.version}</dd>
            </div>
            <div>
              <dt>Status</dt>
              <dd><span className="badge badge-success">{app.status || 'Released'}</span></dd>
            </div>
            <div>
              <dt>Local</dt>
              <dd>{app.installed ? `${app.installed.version} · ${app.installed.status || 'installed'}` : 'Not installed'}</dd>
            </div>
          </dl>

          <div className="component-list">
            <div className="component-list-title">
              <ShieldCheck fontSize={15} aria-hidden="true" />
              Components
            </div>
            {status.state === 'danger' && (
              <div className="table-empty compact-empty danger-text">{status.message}</div>
            )}
            {(components.length ? components : packages).map((item) => (
              <div className="component-item" key={item.componentId || item.id || item.packageId}>
                <div>
                  <strong>{item.componentId || item.code || item.name}</strong>
                  <span>{item.packageName || item.selectedVersion || item.type}</span>
                </div>
                <span className="badge badge-muted">{item.version || item.selectedVersion || item.type || 'pkg'}</span>
              </div>
            ))}
            {!components.length && !packages.length && (
              <div className="table-empty compact-empty">{status.message || 'Chưa có component.'}</div>
            )}
          </div>

          {app.installed && installedServiceStatus !== 'not-checked' && (
            <div className="service-health-panel">
              <div className="component-list-title">
                <Activity fontSize={15} aria-hidden="true" />
                Service health
                <span className={`badge badge-${installedServiceStatus === 'healthy' ? 'success' : installedServiceStatus === 'unhealthy' ? 'danger' : 'muted'}`}>
                  {serviceCheckSummary(installedServiceStatus, installedServiceChecks)}
                </span>
              </div>
              <ServiceChecks
                checks={installedServiceChecks}
                status={installedServiceStatus}
                showSource
              />
            </div>
          )}
        </>
      ) : (
        <div className="table-empty compact-empty">Chọn app để xem manifest.</div>
      )}
    </section>
  );
}

function Toast({ toast }) {
  const tone = toast.type === 'failure' ? 'danger' : toast.type;
  const Icon = tone === 'danger' ? AlertCircle : tone === 'success' ? CheckCircle2 : Activity;
  return (
    <div className={`toast tone-${tone || 'info'}`}>
      <Icon fontSize={17} aria-hidden="true" />
      <span>{toast.message}</span>
    </div>
  );
}

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