import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router';
import {
  AlertDialog,
  Badge,
  Button,
  ConfirmDialog,
  DataTable,
  EmptyState,
  Input,
  MonoText,
  SectionHeader,
  Select,
  StatusDot,
  Tabs,
  Tag,
  Toggle,
  useToast,
} from '@cameleer/design-system';
import type { Column } from '@cameleer/design-system';
import { Shield, Info } from 'lucide-react';
import { EnvEditor } from '../../components/EnvEditor';
import { useEnvironmentStore } from '../../api/environment-store';
import { useEnvironments } from '../../api/queries/admin/environments';
import {
  useApps,
  useCreateApp,
  useDeleteApp,
  useAppVersions,
  useUploadJar,
  useDeployments,
  useCreateDeployment,
  useStopDeployment,
  useUpdateContainerConfig,
} from '../../api/queries/admin/apps';
import type { App, AppVersion, Deployment } from '../../api/queries/admin/apps';
import type { Environment } from '../../api/queries/admin/environments';
import { useApplicationConfig, useUpdateApplicationConfig, useProcessorRouteMapping } from '../../api/queries/commands';
import type { ApplicationConfig, TapDefinition } from '../../api/queries/commands';
import { useCatalog } from '../../api/queries/catalog';
import { useSensitiveKeys } from '../../api/queries/admin/sensitive-keys';
import type { CatalogApp, CatalogRoute } from '../../api/queries/catalog';
import { DeploymentProgress } from '../../components/DeploymentProgress';
import { StartupLogPanel } from '../../components/StartupLogPanel';
import { timeAgo } from '../../utils/format-utils';
import { applyTracedProcessorUpdate, applyRouteRecordingUpdate } from '../../utils/config-draft-utils';
import { PageLoader } from '../../components/PageLoader';
import styles from './AppsTab.module.css';
import sectionStyles from '../../styles/section-card.module.css';
import tableStyles from '../../styles/table-section.module.css';
import skStyles from '../Admin/SensitiveKeysPage.module.css';

function formatBytes(bytes: number): string {
  if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${bytes} B`;
}

const STATUS_COLORS: Record<string, 'success' | 'warning' | 'error' | 'auto' | 'running'> = {
  RUNNING: 'running', STARTING: 'warning', FAILED: 'error', STOPPED: 'auto',
  DEGRADED: 'warning', STOPPING: 'auto',
};

const DEPLOY_STATUS_DOT: Record<string, 'live' | 'stale' | 'dead' | 'success' | 'warning' | 'error' | 'running'> = {
  RUNNING: 'live', STARTING: 'running', DEGRADED: 'stale',
  STOPPING: 'stale', STOPPED: 'dead', FAILED: 'error',
};

function slugify(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .substring(0, 100);
}

export default function AppsTab() {
  const { appId } = useParams<{ appId?: string }>();
  const location = useLocation();
  const selectedEnv = useEnvironmentStore((s) => s.environment);
  const { data: environments = [] } = useEnvironments();

  if (location.pathname.endsWith('/apps/new')) return <CreateAppView environments={environments} selectedEnv={selectedEnv} />;
  if (appId) return <AppDetailView appId={appId} environments={environments} selectedEnv={selectedEnv} />;
  return <AppListView selectedEnv={selectedEnv} environments={environments} />;
}

// ═══════════════════════════════════════════════════════════════════
//  LIST VIEW
// ═══════════════════════════════════════════════════════════════════

function AppListView({ selectedEnv, environments }: { selectedEnv: string | undefined; environments: Environment[] }) {
  const navigate = useNavigate();
  const { data: envApps = [], isLoading: envLoading } = useApps(selectedEnv);
  const { data: catalog = [] } = useCatalog(selectedEnv);

  // Apps are env-scoped; without an env selection there is no managed-app list
  // to show. The Runtime tab (catalog) is the cross-env discovery surface.
  const apps = selectedEnv ? envApps : [];
  const isLoading = selectedEnv ? envLoading : false;

  const envMap = useMemo(() => new Map(environments.map((e) => [e.id, e])), [environments]);

  // Build slug → deployment status map from catalog
  const deployStatusMap = useMemo(() => {
    const map = new Map<string, string>();
    for (const app of catalog as CatalogApp[]) {
      if (app.deployment) map.set(app.slug, app.deployment.status);
    }
    return map;
  }, [catalog]);

  type AppRow = App & { id: string; envName: string };
  const rows: AppRow[] = useMemo(
    () => apps.map((a) => ({ ...a, envName: envMap.get(a.environmentId)?.displayName ?? '?' })),
    [apps, envMap],
  );

  const columns: Column<AppRow>[] = useMemo(() => [
    { key: 'displayName', header: 'Name', sortable: true,
      render: (_v: unknown, row: AppRow) => (
        <div><div className={styles.cellName}>{row.displayName}</div><div className={styles.cellMeta}>{row.slug}</div></div>
      ),
    },
    ...(!selectedEnv ? [{ key: 'envName', header: 'Environment', sortable: true,
      render: (_v: unknown, row: AppRow) => <Badge label={row.envName} color={'auto' as const} />,
    }] : []),
    { key: 'slug', header: 'Status', sortable: false,
      render: (_v: unknown, row: AppRow) => {
        const status = deployStatusMap.get(row.slug);
        if (!status) return <span className={styles.cellMeta}>—</span>;
        const dotVariant = DEPLOY_STATUS_DOT[status] ?? 'dead';
        const badgeColor = STATUS_COLORS[status] ?? 'auto';
        return (
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <StatusDot variant={dotVariant} />
            <Badge label={status} color={badgeColor} />
          </span>
        );
      },
    },
    { key: 'updatedAt', header: 'Updated', sortable: true,
      render: (_v: unknown, row: AppRow) => <span className={styles.cellMeta}>{timeAgo(row.updatedAt)}</span>,
    },
    { key: 'createdAt', header: 'Created', sortable: true,
      render: (_v: unknown, row: AppRow) => <span className={styles.cellMeta}>{new Date(row.createdAt).toLocaleDateString()}</span>,
    },
  ], [selectedEnv, deployStatusMap]);

  if (isLoading) return <PageLoader />;

  return (
    <div className={styles.container}>
      <div className={styles.toolbar}>
        <Button size="sm" variant="primary" onClick={() => navigate('/apps/new')}>+ Create App</Button>
      </div>
      <DataTable columns={columns} data={rows} onRowClick={(row) => navigate(`/apps/${row.slug}`)} />
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════
//  CREATE APP PAGE
// ═══════════════════════════════════════════════════════════════════

function CreateAppView({ environments, selectedEnv }: { environments: Environment[]; selectedEnv: string | undefined }) {
  const { toast } = useToast();
  const navigate = useNavigate();
  const createApp = useCreateApp();
  const uploadJar = useUploadJar();
  const createDeployment = useCreateDeployment();
  const { data: globalKeysConfig } = useSensitiveKeys();
  const globalKeys = globalKeysConfig?.keys ?? [];
  const updateAgentConfig = useUpdateApplicationConfig();
  const updateContainerConfig = useUpdateContainerConfig();

  const defaultEnvId = useMemo(() => environments.find((e) => e.slug === selectedEnv)?.id ?? (environments.length > 0 ? environments[0].id : ''), [environments, selectedEnv]);

  // Identity
  const [name, setName] = useState('');
  const [slugEdited, setSlugEdited] = useState(false);
  const [slug, setSlug] = useState('');
  const [envId, setEnvId] = useState(defaultEnvId);
  const [file, setFile] = useState<File | null>(null);
  const [deploy, setDeploy] = useState(true);
  const fileInputRef = useRef<HTMLInputElement>(null);

  // Monitoring
  const [engineLevel, setEngineLevel] = useState('REGULAR');
  const [payloadCapture, setPayloadCapture] = useState('BOTH');
  const [payloadSize, setPayloadSize] = useState('4');
  const [payloadUnit, setPayloadUnit] = useState('KB');
  const [appLogLevel, setAppLogLevel] = useState('INFO');
  const [agentLogLevel, setAgentLogLevel] = useState('INFO');
  const [metricsEnabled, setMetricsEnabled] = useState(true);
  const [metricsInterval, setMetricsInterval] = useState('60');
  const [samplingRate, setSamplingRate] = useState('1.0');
  const [compressSuccess, setCompressSuccess] = useState(false);
  const [replayEnabled, setReplayEnabled] = useState(true);
  const [routeControlEnabled, setRouteControlEnabled] = useState(true);

  // Resources
  const env = useMemo(() => environments.find((e) => e.id === envId), [environments, envId]);
  const isProd = env?.production ?? false;
  const defaults = env?.defaultContainerConfig ?? {};
  const [memoryLimit, setMemoryLimit] = useState(String(defaults.memoryLimitMb ?? 512));
  const [memoryReserve, setMemoryReserve] = useState(String(defaults.memoryReserveMb ?? ''));
  const [cpuRequest, setCpuRequest] = useState(String(defaults.cpuRequest ?? 500));
  const [cpuLimit, setCpuLimit] = useState(String(defaults.cpuLimit ?? ''));
  const [ports, setPorts] = useState<number[]>(Array.isArray(defaults.exposedPorts) ? defaults.exposedPorts as number[] : []);
  const [newPort, setNewPort] = useState('');
  const [envVars, setEnvVars] = useState<{ key: string; value: string }[]>([]);
  const [appPort, setAppPort] = useState('8080');
  const [replicas, setReplicas] = useState('1');
  const [deployStrategy, setDeployStrategy] = useState('blue-green');
  const [stripPrefix, setStripPrefix] = useState(true);
  const [sslOffloading, setSslOffloading] = useState(true);
  const [runtimeType, setRuntimeType] = useState(String(defaults.runtimeType ?? 'auto'));
  const [customArgs, setCustomArgs] = useState(String(defaults.customArgs ?? ''));
  const [extraNetworks, setExtraNetworks] = useState<string[]>(Array.isArray(defaults.extraNetworks) ? defaults.extraNetworks as string[] : []);
  const [newNetwork, setNewNetwork] = useState('');

  // Sensitive keys
  const [sensitiveKeys, setSensitiveKeys] = useState<string[]>([]);
  const [newSensitiveKey, setNewSensitiveKey] = useState('');

  const [configTab, setConfigTab] = useState<'monitoring' | 'resources' | 'variables' | 'sensitive-keys'>('monitoring');
  const [busy, setBusy] = useState(false);
  const [step, setStep] = useState('');

  // Reset resource defaults when environment changes
  useEffect(() => {
    const d = environments.find((e) => e.id === envId)?.defaultContainerConfig ?? {};
    setMemoryLimit(String(d.memoryLimitMb ?? 512));
    setMemoryReserve(String(d.memoryReserveMb ?? ''));
    setCpuRequest(String(d.cpuRequest ?? 500));
    setCpuLimit(String(d.cpuLimit ?? ''));
    setPorts(Array.isArray(d.exposedPorts) ? d.exposedPorts as number[] : []);
    setRuntimeType(String(d.runtimeType ?? 'auto'));
    setCustomArgs(String(d.customArgs ?? ''));
    setExtraNetworks(Array.isArray(d.extraNetworks) ? d.extraNetworks as string[] : []);
  }, [envId, environments]);

  useEffect(() => {
    if (!slugEdited) setSlug(slugify(name));
  }, [name, slugEdited]);

  function addPort() {
    const p = parseInt(newPort);
    if (p && !ports.includes(p)) { setPorts([...ports, p]); setNewPort(''); }
  }

  const canSubmit = name.trim() && slug.trim() && envId && (file || !deploy);

  async function handleSubmit() {
    if (!canSubmit) return;
    setBusy(true);
    try {
      // 1. Create app
      setStep('Creating app...');
      const app = await createApp.mutateAsync({ envSlug: selectedEnv!, slug: slug.trim(), displayName: name.trim() });

      // 2. Upload JAR (if provided)
      let version: AppVersion | null = null;
      if (file) {
        setStep('Uploading JAR...');
        version = await uploadJar.mutateAsync({ envSlug: selectedEnv!, appSlug: app.slug, file });
      }

      // 3. Save container config
      setStep('Saving configuration...');
      const containerConfig: Record<string, unknown> = {
        memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
        memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
        cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
        cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
        exposedPorts: ports,
        customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
        appPort: appPort ? parseInt(appPort) : 8080,
        replicas: replicas ? parseInt(replicas) : 1,
        deploymentStrategy: deployStrategy,
        stripPathPrefix: stripPrefix,
        sslOffloading: sslOffloading,
        runtimeType: runtimeType,
        customArgs: customArgs || null,
        extraNetworks: extraNetworks,
      };
      await updateContainerConfig.mutateAsync({ envSlug: selectedEnv!, appSlug: app.slug, config: containerConfig });

      // 4. Save agent config (will be pushed to agent on first connect)
      setStep('Saving monitoring config...');
      await updateAgentConfig.mutateAsync({
        config: {
          application: slug.trim(),
          version: 0,
          engineLevel,
          payloadCaptureMode: payloadCapture,
          applicationLogLevel: appLogLevel,
          agentLogLevel,
          metricsEnabled,
          samplingRate: parseFloat(samplingRate) || 1.0,
          compressSuccess,
          tracedProcessors: {},
          taps: [],
          tapVersion: 0,
          routeRecording: {},
          sensitiveKeys: sensitiveKeys.length > 0 ? sensitiveKeys : undefined,
        },
        environment: selectedEnv!,
      });

      // 5. Deploy (if requested and JAR was uploaded)
      if (deploy && version) {
        setStep('Starting deployment...');
        await createDeployment.mutateAsync({ envSlug: selectedEnv!, appSlug: app.slug, appVersionId: version.id });
      }

      toast({ title: deploy ? 'App created and deployed' : 'App created', description: name.trim(), variant: 'success' });
      navigate(`/apps/${app.slug}`);
    } catch (e) {
      toast({ title: 'Failed: ' + step, description: e instanceof Error ? e.message : 'Unknown error', variant: 'error', duration: 86_400_000 });
    } finally {
      setBusy(false);
      setStep('');
    }
  }

  return (
    <div className={styles.container}>
      <div className={styles.detailHeader}>
        <div>
          <h2 className={styles.detailTitle}>Create Application</h2>
          <div className={styles.detailMeta}>Configure and deploy a new application</div>
        </div>
        <div className={styles.detailActions}>
          <Button size="sm" variant="ghost" onClick={() => navigate('/apps')} disabled={busy}>Cancel</Button>
          <Button size="sm" variant="primary" onClick={handleSubmit} loading={busy} disabled={!canSubmit || busy}>
            {deploy ? 'Create & Deploy' : 'Create'}
          </Button>
        </div>
      </div>

      {step && <div className={styles.stepIndicator}>{step}</div>}

      {/* Identity Section */}
      <div className={sectionStyles.section}>
        <SectionHeader>Identity & Artifact</SectionHeader>
        <div className={styles.configGrid}>
          <span className={styles.configLabel}>Application Name</span>
          <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Payment Gateway" disabled={busy} />

          <span className={styles.configLabel}>External URL</span>
          <MonoText size="sm">{(() => {
            const domain = String(defaults.routingDomain ?? '');
            const envSlug = env?.slug ?? '...';
            const appSlug = slug || '...';
            if (defaults.routingMode === 'subdomain' && domain) {
              return `https://${appSlug}-${envSlug}.${domain}/`;
            }
            const base = domain ? `https://${domain}` : window.location.origin;
            return `${base}/${envSlug}/${appSlug}/`;
          })()}</MonoText>

          <span className={styles.configLabel}>Environment</span>
          <Select value={envId} onChange={(e) => setEnvId(e.target.value)} disabled={busy}
            options={environments.filter((e) => e.enabled).map((e) => ({ value: e.id, label: `${e.displayName} (${e.slug})` }))} />

          <span className={styles.configLabel}>Application JAR</span>
          <div className={styles.fileRow}>
            <input ref={fileInputRef} type="file" accept=".jar"
              className={styles.visuallyHidden}
              onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
            <Button size="sm" variant="secondary" type="button" onClick={() => fileInputRef.current?.click()} disabled={busy}>
              {file ? 'Change file' : 'Select JAR'}
            </Button>
            {file && <span className={styles.fileName}>{file.name} ({formatBytes(file.size)})</span>}
          </div>

          <span className={styles.configLabel}>Deploy</span>
          <div className={styles.configInline}>
            <Toggle checked={deploy} onChange={() => setDeploy(!deploy)} disabled={busy} />
            <span className={deploy ? styles.toggleEnabled : styles.toggleDisabled}>
              {deploy ? 'Deploy immediately after creation' : 'Create only (deploy later)'}
            </span>
          </div>
        </div>
      </div>

      {/* Config Tabs */}
      <Tabs
        tabs={[
          { label: 'Monitoring', value: 'monitoring' },
          { label: 'Resources', value: 'resources' },
          { label: 'Variables', value: 'variables' },
          { label: 'Sensitive Keys', value: 'sensitive-keys' },
        ]}
        active={configTab}
        onChange={(v) => setConfigTab(v as typeof configTab)}
      />

      {configTab === 'variables' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Variables</SectionHeader>
          <EnvEditor value={envVars} onChange={setEnvVars} disabled={busy} />
        </div>
      )}

      {configTab === 'monitoring' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Monitoring</SectionHeader>
          <div className={styles.configGrid}>
            <span className={styles.configLabel}>Engine Level</span>
            <Select disabled={busy} value={engineLevel} onChange={(e) => setEngineLevel(e.target.value)}
              options={[{ value: 'NONE', label: 'NONE' }, { value: 'MINIMAL', label: 'MINIMAL' }, { value: 'REGULAR', label: 'REGULAR' }, { value: 'COMPLETE', label: 'COMPLETE' }]} />

            <span className={styles.configLabel}>Payload Capture</span>
            <Select disabled={busy} value={payloadCapture} onChange={(e) => setPayloadCapture(e.target.value)}
              options={[{ value: 'NONE', label: 'NONE' }, { value: 'INPUT', label: 'INPUT' }, { value: 'OUTPUT', label: 'OUTPUT' }, { value: 'BOTH', label: 'BOTH' }]} />

            <span className={styles.configLabel}>Max Payload Size</span>
            <div className={styles.configInline}>
              <Input disabled={busy} value={payloadSize} onChange={(e) => setPayloadSize(e.target.value)} className={styles.inputMd} placeholder="e.g. 4" />
              <Select disabled={busy} value={payloadUnit} onChange={(e) => setPayloadUnit(e.target.value)} className={styles.inputXl}
                options={[{ value: 'bytes', label: 'bytes' }, { value: 'KB', label: 'KB' }, { value: 'MB', label: 'MB' }]} />
            </div>

            <span className={styles.configLabel}>App Log Level</span>
            <Select disabled={busy} value={appLogLevel} onChange={(e) => setAppLogLevel(e.target.value)}
              options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} />

            <span className={styles.configLabel}>Agent Log Level</span>
            <Select disabled={busy} value={agentLogLevel} onChange={(e) => setAgentLogLevel(e.target.value)}
              options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} />

            <span className={styles.configLabel}>Metrics</span>
            <div className={styles.configInline}>
              <Toggle checked={metricsEnabled} onChange={() => !busy && setMetricsEnabled(!metricsEnabled)} disabled={busy} />
              <span className={metricsEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{metricsEnabled ? 'Enabled' : 'Disabled'}</span>
              <span className={styles.cellMeta} style={{ marginLeft: 8 }}>Interval</span>
              <Input disabled={busy} value={metricsInterval} onChange={(e) => setMetricsInterval(e.target.value)} className={styles.inputXs} placeholder="60" />
              <span className={styles.cellMeta}>s</span>
            </div>

            <span className={styles.configLabel}>Sampling Rate</span>
            <Input disabled={busy} value={samplingRate} onChange={(e) => setSamplingRate(e.target.value)} className={styles.inputLg} placeholder="1.0" />

            <span className={styles.configLabel}>Compress Success</span>
            <div className={styles.configInline}>
              <Toggle checked={compressSuccess} onChange={() => !busy && setCompressSuccess(!compressSuccess)} disabled={busy} />
              <span className={compressSuccess ? styles.toggleEnabled : styles.toggleDisabled}>{compressSuccess ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Replay</span>
            <div className={styles.configInline}>
              <Toggle checked={replayEnabled} onChange={() => !busy && setReplayEnabled(!replayEnabled)} disabled={busy} />
              <span className={replayEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{replayEnabled ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Route Control</span>
            <div className={styles.configInline}>
              <Toggle checked={routeControlEnabled} onChange={() => !busy && setRouteControlEnabled(!routeControlEnabled)} disabled={busy} />
              <span className={routeControlEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{routeControlEnabled ? 'Enabled' : 'Disabled'}</span>
            </div>

          </div>
        </div>
      )}

      {configTab === 'resources' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Container Resources</SectionHeader>
          <div className={styles.configGrid}>
            <span className={styles.configLabel}>Runtime Type</span>
            <Select disabled={busy} value={runtimeType} onChange={(e) => setRuntimeType(e.target.value)}
              options={[
                { value: 'auto', label: 'Auto (detect from JAR)' },
                { value: 'spring-boot', label: 'Spring Boot' },
                { value: 'quarkus', label: 'Quarkus' },
                { value: 'plain-java', label: 'Plain Java' },
                { value: 'native', label: 'Native' },
              ]} />

            <span className={styles.configLabel}>Custom Arguments</span>
            <div>
              <Input disabled={busy} value={customArgs} onChange={(e) => setCustomArgs(e.target.value)}
                placeholder="-Xmx256m -Dfoo=bar" className={styles.inputLg} />
              <span className={styles.configHint}>
                {runtimeType === 'native' ? 'Arguments passed to the native binary' : 'Additional JVM arguments appended to the start command'}
              </span>
            </div>

            <span className={styles.configLabel}>Memory Limit</span>
            <div className={styles.configInline}>
              <Input disabled={busy} value={memoryLimit} onChange={(e) => setMemoryLimit(e.target.value)} className={styles.inputLg} placeholder="e.g. 512" />
              <span className={styles.cellMeta}>MB</span>
            </div>

            <span className={styles.configLabel}>Memory Reserve</span>
            <div>
              <div className={styles.configInline}>
                <Input disabled={!isProd || busy} value={memoryReserve} onChange={(e) => setMemoryReserve(e.target.value)} placeholder="e.g. 256" className={styles.inputLg} />
                <span className={styles.cellMeta}>MB</span>
              </div>
              {!isProd && <span className={styles.configHint}>Available in production environments only</span>}
            </div>

            <span className={styles.configLabel}>CPU Request</span>
            <Input disabled={busy} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} className={styles.inputLg} placeholder="e.g. 500 millicores" />

            <span className={styles.configLabel}>CPU Limit</span>
            <div className={styles.configInline}>
              <Input disabled={busy} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" className={styles.inputLg} />
              <span className={styles.cellMeta}>millicores</span>
            </div>

            <span className={styles.configLabel}>Exposed Ports</span>
            <div className={styles.portPills}>
              {ports.map((p) => (
                <span key={p} className={styles.portPill}>
                  {p}
                  <button className={styles.portPillDelete} disabled={busy}
                    onClick={() => !busy && setPorts(ports.filter((x) => x !== p))}>&times;</button>
                </span>
              ))}
              <input className={styles.portAddInput} disabled={busy} placeholder="+ port" value={newPort}
                onChange={(e) => setNewPort(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addPort(); } }} />
            </div>

            <span className={styles.configLabel}>App Port</span>
            <Input disabled={busy} value={appPort} onChange={(e) => setAppPort(e.target.value)} className={styles.inputLg} placeholder="e.g. 8080" />

            <span className={styles.configLabel}>Replicas</span>
            <Input disabled={busy} value={replicas} onChange={(e) => setReplicas(e.target.value)} className={styles.inputSm} type="number" placeholder="1" />

            <span className={styles.configLabel}>Deploy Strategy</span>
            <Select disabled={busy} value={deployStrategy} onChange={(e) => setDeployStrategy(e.target.value)}
              options={[{ value: 'blue-green', label: 'Blue/Green' }, { value: 'rolling', label: 'Rolling' }]} />

            <span className={styles.configLabel}>Strip Path Prefix</span>
            <div className={styles.configInline}>
              <Toggle checked={stripPrefix} onChange={() => !busy && setStripPrefix(!stripPrefix)} disabled={busy} />
              <span className={stripPrefix ? styles.toggleEnabled : styles.toggleDisabled}>{stripPrefix ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>SSL Offloading</span>
            <div className={styles.configInline}>
              <Toggle checked={sslOffloading} onChange={() => !busy && setSslOffloading(!sslOffloading)} disabled={busy} />
              <span className={sslOffloading ? styles.toggleEnabled : styles.toggleDisabled}>{sslOffloading ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Extra Networks</span>
            <div>
              <div className={styles.portPills}>
                {extraNetworks.map((n) => (
                  <span key={n} className={styles.portPill}>
                    {n}
                    <button className={styles.portPillDelete} disabled={busy}
                      onClick={() => !busy && setExtraNetworks(extraNetworks.filter((x) => x !== n))}>&times;</button>
                  </span>
                ))}
                <input className={styles.portAddInput} disabled={busy} placeholder="+ network" value={newNetwork}
                  onChange={(e) => setNewNetwork(e.target.value)}
                  onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); const v = newNetwork.trim(); if (v && !extraNetworks.includes(v)) { setExtraNetworks([...extraNetworks, v]); setNewNetwork(''); } } }} />
              </div>
              <span className={styles.configHint}>Additional Docker networks to join (e.g., monitoring, prometheus)</span>
            </div>
          </div>
        </div>
      )}

      {configTab === 'sensitive-keys' && (
        <div className={sectionStyles.section}>
          <div className={skStyles.sectionTitle}>
            <Shield size={14} />
            <span>Agent built-in defaults</span>
          </div>
          <div className={skStyles.defaultsList}>
            {['Authorization', 'Cookie', 'Set-Cookie', 'X-API-Key', 'X-Auth-Token', 'Proxy-Authorization'].map((key) => (
              <Badge key={key} label={key} variant="outlined" />
            ))}
          </div>

          {globalKeys.length > 0 && (
            <>
              <hr style={{ border: 'none', borderTop: '1px solid var(--border-subtle)', margin: '10px 0' }} />
              <div className={skStyles.sectionTitle}>
                <span>Global keys (enforced)</span>
                <span className={skStyles.keyCount}>{globalKeys.length}</span>
              </div>
              <div className={skStyles.defaultsList}>
                {globalKeys.map((key) => (
                  <Badge key={key} label={key} color="auto" variant="filled" />
                ))}
              </div>
            </>
          )}

          <hr style={{ border: 'none', borderTop: '1px solid var(--border-subtle)', margin: '10px 0' }} />
          <div className={skStyles.sectionTitle}>
            <span>Application-specific keys</span>
            {sensitiveKeys.length > 0 && <span className={skStyles.keyCount}>{sensitiveKeys.length}</span>}
          </div>

          <div className={skStyles.pillList}>
            {sensitiveKeys.map((k, i) => (
              <Tag key={`${k}-${i}`} label={k} onRemove={() => !busy && setSensitiveKeys(sensitiveKeys.filter((_, idx) => idx !== i))} />
            ))}
            {sensitiveKeys.length === 0 && (
              <span className={skStyles.emptyState}>No app-specific keys — agents use built-in defaults{globalKeys.length > 0 ? ' and global keys' : ''}</span>
            )}
          </div>

          <div className={skStyles.inputRow}>
            <Input
              value={newSensitiveKey}
              onChange={(e) => setNewSensitiveKey(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter') {
                  e.preventDefault();
                  const v = newSensitiveKey.trim();
                  if (v && !sensitiveKeys.some((k) => k.toLowerCase() === v.toLowerCase())) {
                    setSensitiveKeys([...sensitiveKeys, v]);
                    setNewSensitiveKey('');
                  }
                }
              }}
              placeholder="Add key or glob pattern (e.g. *password*)"
              disabled={busy}
            />
            <Button variant="secondary" size="sm" disabled={busy || !newSensitiveKey.trim()}
              onClick={() => {
                const v = newSensitiveKey.trim();
                if (v && !sensitiveKeys.some((k) => k.toLowerCase() === v.toLowerCase())) {
                  setSensitiveKeys([...sensitiveKeys, v]);
                  setNewSensitiveKey('');
                }
              }}>
              Add
            </Button>
          </div>

          <div className={skStyles.hint}>
            <Info size={12} />
            <span>
              The final masking configuration is: agent defaults + global keys + app-specific keys.
              Supports exact header names and glob patterns.
            </span>
          </div>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════
//  DETAIL VIEW
// ═══════════════════════════════════════════════════════════════════

function AppDetailView({ appId: appSlug, environments, selectedEnv }: { appId: string; environments: Environment[]; selectedEnv: string | undefined }) {
  const { toast } = useToast();
  const navigate = useNavigate();
  const { data: envApps = [] } = useApps(selectedEnv);
  const app = useMemo(() => envApps.find((a) => a.slug === appSlug), [envApps, appSlug]);
  const { data: catalogApps } = useCatalog(selectedEnv);
  const catalogEntry = useMemo(() => (catalogApps ?? []).find((c: CatalogApp) => c.slug === appSlug), [catalogApps, appSlug]);
  const { data: versions = [] } = useAppVersions(selectedEnv, appSlug);
  const { data: deployments = [] } = useDeployments(selectedEnv, appSlug);
  const uploadJar = useUploadJar();
  const createDeployment = useCreateDeployment();
  const stopDeployment = useStopDeployment();
  const deleteApp = useDeleteApp();
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [subTab, setSubTab] = useState<'overview' | 'config'>('config');
  const [deleteConfirm, setDeleteConfirm] = useState(false);
  const [stopTarget, setStopTarget] = useState<{ id: string; name: string } | null>(null);

  const envMap = useMemo(() => new Map(environments.map((e) => [e.id, e])), [environments]);
  const sortedVersions = useMemo(() => [...versions].sort((a, b) => b.version - a.version), [versions]);

  if (!app) {
    return (
      <div style={{ padding: '40px 24px' }}>
        <EmptyState
          title="Unmanaged Application"
          description={`"${appSlug}" is a discovered application registered by agents. To manage deployments, create it as a managed app first.`}
        />
        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 16 }}>
          <Button variant="primary" onClick={() => navigate('/apps/new')}>Create Managed App</Button>
        </div>
      </div>
    );
  }

  const env = envMap.get(app.environmentId);

  async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    try {
      const v = await uploadJar.mutateAsync({ envSlug: selectedEnv!, appSlug, file });
      toast({ title: `Version ${v.version} uploaded`, description: file.name, variant: 'success' });
    } catch { toast({ title: 'Failed to upload JAR', variant: 'error', duration: 86_400_000 }); }
    if (fileInputRef.current) fileInputRef.current.value = '';
  }

  async function handleDeploy(versionId: string) {
    try {
      await createDeployment.mutateAsync({ envSlug: selectedEnv!, appSlug, appVersionId: versionId });
      toast({ title: 'Deployment started', variant: 'success' });
    } catch { toast({ title: 'Failed to deploy application', variant: 'error', duration: 86_400_000 }); }
  }

  function handleStop(deploymentId: string) {
    setStopTarget({ id: deploymentId, name: app?.displayName ?? appSlug });
  }

  async function confirmStop() {
    if (!stopTarget) return;
    try {
      await stopDeployment.mutateAsync({ envSlug: selectedEnv!, appSlug, deploymentId: stopTarget.id });
      toast({ title: 'Deployment stopped', variant: 'warning' });
    } catch { toast({ title: 'Failed to stop deployment', variant: 'error', duration: 86_400_000 }); }
    setStopTarget(null);
  }

  async function handleDelete() {
    try {
      await deleteApp.mutateAsync({ envSlug: selectedEnv!, appSlug });
      toast({ title: 'App deleted', variant: 'warning' });
      navigate('/apps');
    } catch { toast({ title: 'Delete failed', variant: 'error', duration: 86_400_000 }); }
  }

  return (
    <div className={styles.container}>
      <div className={styles.detailHeader}>
        <div>
          <h2 className={styles.detailTitle}>
            {catalogEntry && (
              <span title={catalogEntry.healthTooltip} style={{ marginRight: 6, verticalAlign: 'middle' }}>
                <StatusDot variant={catalogEntry.health === 'offline' ? 'dead' : catalogEntry.health as any} />
              </span>
            )}
            {app.displayName}
          </h2>
          <div className={styles.detailMeta}>
            {app.slug} &middot; <Badge label={env?.displayName ?? '?'} color="auto" />
            {catalogEntry?.deployment && (
              <> &middot; <Badge label={catalogEntry.deployment.status} color={STATUS_COLORS[catalogEntry.deployment.status as keyof typeof STATUS_COLORS] ?? 'auto'} /></>
            )}
          </div>
        </div>
        <div className={styles.detailActions}>
          <input ref={fileInputRef} type="file" accept=".jar"
            className={styles.visuallyHidden}
            onChange={handleUpload} />
          <Button size="sm" variant="primary" type="button" onClick={() => fileInputRef.current?.click()} loading={uploadJar.isPending}>Upload JAR</Button>
          <Button size="sm" variant="danger" onClick={() => setDeleteConfirm(true)}>Delete App</Button>
        </div>
      </div>

      <Tabs
        tabs={[
          { label: 'Configuration', value: 'config' },
          { label: 'Overview', value: 'overview' },
        ]}
        active={subTab}
        onChange={(v) => setSubTab(v as typeof subTab)}
      />

      {subTab === 'overview' && (
        <OverviewSubTab
          app={app} deployments={deployments} versions={sortedVersions}
          environments={environments} envMap={envMap} selectedEnv={selectedEnv}
          onDeploy={handleDeploy} onStop={handleStop}
        />
      )}
      {subTab === 'config' && (
        <ConfigSubTab app={app} environment={env} />
      )}

      <ConfirmDialog
        open={deleteConfirm}
        onClose={() => setDeleteConfirm(false)}
        onConfirm={handleDelete}
        message={`Delete app "${app.displayName}"? All versions and deployments will be removed. This cannot be undone.`}
        confirmText={app.slug}
        loading={deleteApp.isPending}
      />
      <AlertDialog
        open={!!stopTarget}
        onClose={() => setStopTarget(null)}
        onConfirm={confirmStop}
        title="Stop deployment?"
        description={`Stop deployment for "${stopTarget?.name}"? This will take the service offline.`}
        confirmLabel="Stop"
        variant="warning"
      />
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════
//  OVERVIEW SUB-TAB
// ═══════════════════════════════════════════════════════════════════

function OverviewSubTab({ app, deployments, versions, environments, envMap, selectedEnv, onDeploy, onStop }: {
  app: App; deployments: Deployment[]; versions: AppVersion[];
  environments: Environment[]; envMap: Map<string, Environment>;
  selectedEnv: string | undefined;
  onDeploy: (versionId: string, envId: string) => void;
  onStop: (deploymentId: string) => void;
}) {
  // Determine which env slug is selected
  const selectedEnvId = useMemo(
    () => selectedEnv ? environments.find((e) => e.slug === selectedEnv)?.id : undefined,
    [environments, selectedEnv],
  );

  type DeploymentRow = Deployment & {
    dEnv: Environment | undefined;
    version: AppVersion | undefined;
    isSelectedEnv: boolean;
    canAct: boolean;
    canStart: boolean;
    configChanged: boolean;
    url: string;
  };

  const deploymentRows: DeploymentRow[] = useMemo(() => deployments.map((d) => {
    const dEnv = envMap.get(d.environmentId);
    const version = versions.find((v) => v.id === d.appVersionId);
    const isSelectedEnv = !selectedEnvId || d.environmentId === selectedEnvId;
    const canAct = isSelectedEnv && (d.status === 'RUNNING' || d.status === 'STARTING');
    const canStart = isSelectedEnv && d.status === 'STOPPED';
    const configChanged = canAct && !!d.deployedAt && new Date(app.updatedAt) > new Date(d.deployedAt);
    const url = dEnv ? `/${dEnv.slug}/${app.slug}/` : '';
    return { ...d, dEnv, version, isSelectedEnv, canAct, canStart, configChanged, url };
  }), [deployments, envMap, versions, selectedEnvId, app]);

  const deploymentColumns: Column<DeploymentRow>[] = useMemo(() => [
    { key: 'environmentId', header: 'Environment', render: (_v, row) => (
      <span style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        <Badge label={row.dEnv?.displayName ?? '?'} color={row.dEnv?.production ? 'error' : 'auto'} />
      </span>
    )},
    { key: 'appVersionId', header: 'Version', render: (_v, row) => (
      <span style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        <Badge label={row.version ? `v${row.version.version}` : '?'} color="auto" />
      </span>
    )},
    { key: 'status', header: 'Status', render: (_v, row) => (
      <div className={styles.cellFlex} style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        <StatusDot variant={DEPLOY_STATUS_DOT[row.status] ?? 'dead'} />
        <Badge label={row.status} color={STATUS_COLORS[row.status] ?? 'auto'} />
      </div>
    )},
    { key: 'replicaStates', header: 'Replicas', render: (_v, row) => (
      <span style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        {row.replicaStates && row.replicaStates.length > 0
          ? <span className={styles.cellMeta}>{row.replicaStates.filter((r) => r.status === 'RUNNING').length}/{row.replicaStates.length}</span>
          : <>{'—'}</>}
      </span>
    )},
    { key: 'url' as any, header: 'URL', render: (_v, row) => (
      <span style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        {row.status === 'RUNNING'
          ? <MonoText size="xs">{row.url}</MonoText>
          : <span className={styles.mutedMono}>{row.url}</span>}
      </span>
    )},
    { key: 'deployedAt', header: 'Deployed', render: (_v, row) => (
      <span className={styles.cellMeta} style={{ opacity: row.isSelectedEnv ? 1 : 0.45 }}>
        {row.deployedAt ? timeAgo(row.deployedAt) : '—'}
      </span>
    )},
    { key: 'actions' as any, header: '', render: (_v, row) => (
      <div className={styles.cellFlex} style={{ justifyContent: 'flex-end', gap: 4 }}>
        {row.configChanged && <Button size="sm" variant="primary" onClick={() => onDeploy(row.appVersionId, row.environmentId)}>Redeploy</Button>}
        {row.canAct && <Button size="sm" variant="danger" onClick={() => onStop(row.id)}>Stop</Button>}
        {row.canStart && <Button size="sm" variant="secondary" onClick={() => onDeploy(row.appVersionId, row.environmentId)}>Start</Button>}
        {!row.isSelectedEnv && <span className={styles.envHint}>switch env to manage</span>}
      </div>
    )},
  ], [onDeploy, onStop]);

  return (
    <>
      <div className={tableStyles.tableSection}>
        <div className={tableStyles.tableHeader}>
          <span className={tableStyles.tableTitle}>Deployments</span>
        </div>
        {deploymentRows.length === 0
          ? <EmptyState title="No deployments" description="Deploy this application to see deployment history." />
          : <DataTable<DeploymentRow> columns={deploymentColumns} data={deploymentRows} flush />
        }
      </div>
      {deployments.filter((d) => d.deployStage || d.status === 'FAILED').map((d) => (
        <div key={`progress-${d.id}`} style={{ marginBottom: 8 }}>
          <span className={styles.cellMeta}>{d.containerName}</span>
          <DeploymentProgress currentStage={d.deployStage} failed={d.status === 'FAILED'} />
          <StartupLogPanel deployment={d} appSlug={app.slug} envSlug={environments.find(e => e.id === d.environmentId)?.slug ?? ''} />
        </div>
      ))}

      <SectionHeader>Versions ({versions.length})</SectionHeader>
      {versions.length === 0 && <EmptyState title="No versions" description="Upload a JAR to create the first version." />}
      {versions.map((v) => (
        <VersionRow key={v.id} version={v} environments={environments} onDeploy={(envId) => onDeploy(v.id, envId)} />
      ))}
    </>
  );
}

function VersionRow({ version, environments, onDeploy }: { version: AppVersion; environments: Environment[]; onDeploy: (envId: string) => void }) {
  const [deployEnv, setDeployEnv] = useState('');
  return (
    <div className={styles.row}>
      <Badge label={`v${version.version}`} color="auto" />
      <span className={styles.rowText}>{version.jarFilename} ({formatBytes(version.jarSizeBytes)})</span>
      <MonoText size="xs">{version.jarChecksum.substring(0, 8)}</MonoText>
      <span className={styles.cellMeta}>{timeAgo(version.uploadedAt)}</span>
      <select className={styles.nativeSelect} value={deployEnv} onChange={(e) => setDeployEnv(e.target.value)}>
        <option value="">Deploy to...</option>
        {environments.filter((e) => e.enabled).map((e) => <option key={e.id} value={e.id}>{e.displayName}</option>)}
      </select>
      <Button size="sm" variant="secondary" disabled={!deployEnv} onClick={() => { onDeploy(deployEnv); setDeployEnv(''); }}>Deploy</Button>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════════════
//  CONFIGURATION SUB-TAB
// ═══════════════════════════════════════════════════════════════════

interface TracedTapRow { id: string; processorId: string; captureMode: string | null; taps: TapDefinition[]; }
interface RouteRecordingRow { id: string; routeId: string; recording: boolean; }

function ConfigSubTab({ app, environment }: { app: App; environment?: Environment }) {
  const { toast } = useToast();
  const navigate = useNavigate();
  const envSlug = environment?.slug;
  const { data: agentConfig } = useApplicationConfig(app.slug, envSlug);
  const updateAgentConfig = useUpdateApplicationConfig();
  const updateContainerConfig = useUpdateContainerConfig();
  const { data: catalog } = useCatalog();
  const { data: processorToRoute = {} } = useProcessorRouteMapping(app.slug, envSlug);
  const isProd = environment?.production ?? false;
  const [editing, setEditing] = useState(false);
  const [configTab, setConfigTab] = useState<'monitoring' | 'resources' | 'variables' | 'traces' | 'recording'>('monitoring');

  const appRoutes: CatalogRoute[] = useMemo(() => {
    if (!catalog) return [];
    const entry = (catalog as CatalogApp[]).find((e) => e.slug === app.slug);
    return entry?.routes ?? [];
  }, [catalog, app.slug]);

  // Agent config state
  const [engineLevel, setEngineLevel] = useState('REGULAR');
  const [payloadCapture, setPayloadCapture] = useState('BOTH');
  const [payloadSize, setPayloadSize] = useState('4');
  const [payloadUnit, setPayloadUnit] = useState('KB');
  const [appLogLevel, setAppLogLevel] = useState('INFO');
  const [agentLogLevel, setAgentLogLevel] = useState('INFO');
  const [metricsEnabled, setMetricsEnabled] = useState(true);
  const [metricsInterval, setMetricsInterval] = useState('60');
  const [samplingRate, setSamplingRate] = useState('1.0');
  const [replayEnabled, setReplayEnabled] = useState(true);
  const [routeControlEnabled, setRouteControlEnabled] = useState(true);
  const [compressSuccess, setCompressSuccess] = useState(false);
  const [tracedDraft, setTracedDraft] = useState<Record<string, string>>({});
  const [routeRecordingDraft, setRouteRecordingDraft] = useState<Record<string, boolean>>({});

  // Container config state
  const defaults = environment?.defaultContainerConfig ?? {};
  const merged = useMemo(() => ({ ...defaults, ...app.containerConfig }), [defaults, app.containerConfig]);
  const [memoryLimit, setMemoryLimit] = useState('512');
  const [memoryReserve, setMemoryReserve] = useState('');
  const [cpuRequest, setCpuRequest] = useState('500');
  const [cpuLimit, setCpuLimit] = useState('');
  const [ports, setPorts] = useState<number[]>([]);
  const [newPort, setNewPort] = useState('');
  const [envVars, setEnvVars] = useState<{ key: string; value: string }[]>([]);
  const [appPort, setAppPort] = useState('8080');
  const [replicas, setReplicas] = useState('1');
  const [deployStrategy, setDeployStrategy] = useState('blue-green');
  const [stripPrefix, setStripPrefix] = useState(true);
  const [sslOffloading, setSslOffloading] = useState(true);
  const [runtimeType, setRuntimeType] = useState(String(merged.runtimeType ?? 'auto'));
  const [customArgs, setCustomArgs] = useState(String(merged.customArgs ?? ''));
  const [extraNetworks, setExtraNetworks] = useState<string[]>(Array.isArray(merged.extraNetworks) ? merged.extraNetworks as string[] : []);
  const [newNetwork, setNewNetwork] = useState('');

  // Versions query for runtime detection hints
  const { data: versions = [] } = useAppVersions(environment?.slug, app.slug);
  const latestVersion = versions?.[0] ?? null;

  // Sync from server data
  const syncFromServer = useCallback(() => {
    if (agentConfig) {
      setEngineLevel(agentConfig.engineLevel ?? 'REGULAR');
      setPayloadCapture(agentConfig.payloadCaptureMode ?? 'BOTH');
      setPayloadSize('4'); setPayloadUnit('KB');
      setAppLogLevel(agentConfig.applicationLogLevel ?? 'INFO');
      setAgentLogLevel(agentConfig.agentLogLevel ?? 'INFO');
      setMetricsEnabled(agentConfig.metricsEnabled);
      setSamplingRate(String(agentConfig.samplingRate));
      setCompressSuccess(agentConfig.compressSuccess);
      setTracedDraft({ ...agentConfig.tracedProcessors });
      setRouteRecordingDraft({ ...agentConfig.routeRecording });
    }
    setMemoryLimit(String(merged.memoryLimitMb ?? 512));
    setMemoryReserve(String(merged.memoryReserveMb ?? ''));
    setCpuRequest(String(merged.cpuRequest ?? 500));
    setCpuLimit(String(merged.cpuLimit ?? ''));
    setPorts(Array.isArray(merged.exposedPorts) ? merged.exposedPorts as number[] : []);
    const vars = merged.customEnvVars as Record<string, string> | undefined;
    setEnvVars(vars ? Object.entries(vars).map(([key, value]) => ({ key, value })) : []);
    setAppPort(String(merged.appPort ?? 8080));
    setReplicas(String(merged.replicas ?? 1));
    setDeployStrategy(String(merged.deploymentStrategy ?? 'blue-green'));
    setStripPrefix(merged.stripPathPrefix !== false);
    setSslOffloading(merged.sslOffloading !== false);
    setRuntimeType(String(merged.runtimeType ?? 'auto'));
    setCustomArgs(String(merged.customArgs ?? ''));
    setExtraNetworks(Array.isArray(merged.extraNetworks) ? merged.extraNetworks as string[] : []);
  }, [agentConfig, merged]);

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

  function handleCancel() {
    syncFromServer();
    setEditing(false);
  }

  function updateTracedProcessor(processorId: string, mode: string) {
    setTracedDraft((prev) => applyTracedProcessorUpdate(prev, processorId, mode));
  }

  function updateRouteRecording(routeId: string, recording: boolean) {
    setRouteRecordingDraft((prev) => applyRouteRecordingUpdate(prev, routeId, recording));
  }

  async function handleSave() {
    // Save agent config
    if (agentConfig) {
      try {
        await updateAgentConfig.mutateAsync({
          config: {
            ...agentConfig,
            engineLevel, payloadCaptureMode: payloadCapture,
            applicationLogLevel: appLogLevel, agentLogLevel,
            metricsEnabled, samplingRate: parseFloat(samplingRate) || 1.0,
            compressSuccess,
            tracedProcessors: tracedDraft,
            routeRecording: routeRecordingDraft,
          },
          environment: environment?.slug,
        });
      } catch { toast({ title: 'Failed to save agent config', variant: 'error', duration: 86_400_000 }); return; }
    }

    // Save container config
    const containerConfig: Record<string, unknown> = {
      memoryLimitMb: memoryLimit ? parseInt(memoryLimit) : null,
      memoryReserveMb: memoryReserve ? parseInt(memoryReserve) : null,
      cpuRequest: cpuRequest ? parseInt(cpuRequest) : null,
      cpuLimit: cpuLimit ? parseInt(cpuLimit) : null,
      exposedPorts: ports,
      customEnvVars: Object.fromEntries(envVars.filter((v) => v.key.trim()).map((v) => [v.key, v.value])),
      appPort: appPort ? parseInt(appPort) : 8080,
      replicas: replicas ? parseInt(replicas) : 1,
      deploymentStrategy: deployStrategy,
      stripPathPrefix: stripPrefix,
      sslOffloading: sslOffloading,
      runtimeType: runtimeType,
      customArgs: customArgs || null,
      extraNetworks: extraNetworks,
    };
    try {
      await updateContainerConfig.mutateAsync({ envSlug: environment?.slug ?? '', appSlug: app.slug, config: containerConfig });
      toast({ title: 'Configuration saved', description: 'Redeploy to apply changes to running deployments.', variant: 'success' });
      setEditing(false);
    } catch { toast({ title: 'Failed to save container config', variant: 'error', duration: 86_400_000 }); }
  }

  function addPort() {
    const p = parseInt(newPort);
    if (p && !ports.includes(p)) { setPorts([...ports, p]); setNewPort(''); }
  }

  // Traces & Taps
  const tracedTapRows: TracedTapRow[] = useMemo(() => {
    const traced = editing ? tracedDraft : (agentConfig?.tracedProcessors ?? {});
    const taps = agentConfig?.taps ?? [];
    const pids = new Set<string>([...Object.keys(traced), ...taps.map(t => t.processorId)]);
    return Array.from(pids).sort().map(pid => ({ id: pid, processorId: pid, captureMode: traced[pid] ?? null, taps: taps.filter(t => t.processorId === pid) }));
  }, [editing, tracedDraft, agentConfig?.tracedProcessors, agentConfig?.taps]);

  const tracedCount = useMemo(() => Object.keys(editing ? tracedDraft : (agentConfig?.tracedProcessors ?? {})).length, [editing, tracedDraft, agentConfig?.tracedProcessors]);
  const tapCount = agentConfig?.taps?.length ?? 0;

  const tracedTapColumns: Column<TracedTapRow>[] = useMemo(() => [
    { key: 'route' as any, header: 'Route', render: (_v: unknown, row: TracedTapRow) => {
      const routeId = processorToRoute[row.processorId];
      return routeId ? <span className={styles.routeLabel}>{routeId}</span> : <span className={styles.hint}>&mdash;</span>;
    }},
    { key: 'processorId', header: 'Processor', render: (_v: unknown, row: TracedTapRow) => <MonoText size="xs">{row.processorId}</MonoText> },
    {
      key: 'captureMode', header: 'Capture',
      render: (_v: unknown, row: TracedTapRow) => {
        if (row.captureMode === null) return <span className={styles.hint}>&mdash;</span>;
        if (editing) return (
          <select className={styles.nativeSelect} value={row.captureMode} onChange={(e) => updateTracedProcessor(row.processorId, e.target.value)}>
            <option value="NONE">None</option><option value="INPUT">Input</option><option value="OUTPUT">Output</option><option value="BOTH">Both</option>
          </select>
        );
        return <Badge label={row.captureMode} color={row.captureMode === 'BOTH' ? 'running' : row.captureMode === 'NONE' ? 'auto' : 'warning'} variant="filled" />;
      },
    },
    {
      key: 'taps', header: 'Taps',
      render: (_v: unknown, row: TracedTapRow) => row.taps.length === 0
        ? <span className={styles.hint}>&mdash;</span>
        : <div className={styles.tapBadges}>{row.taps.map(t => (
            <button key={t.tapId} className={styles.tapBadgeLink} title={`Manage tap on route page`}>
              <Badge label={t.attributeName} color={t.enabled ? 'success' : 'auto'} variant="filled" />
            </button>
          ))}</div>,
    },
    ...(editing ? [{
      key: '_remove' as const, header: '', width: '36px',
      render: (_v: unknown, row: TracedTapRow) => row.captureMode === null ? null : (
        <button className={styles.removeBtn} title="Remove" onClick={() => updateTracedProcessor(row.processorId, 'REMOVE')}>&times;</button>
      ),
    }] : []),
  ], [editing, processorToRoute]);

  // Route Recording
  const routeRecordingRows: RouteRecordingRow[] = useMemo(() => {
    const rec = editing ? routeRecordingDraft : (agentConfig?.routeRecording ?? {});
    return appRoutes.map(r => ({ id: r.routeId, routeId: r.routeId, recording: rec[r.routeId] !== false }));
  }, [editing, routeRecordingDraft, agentConfig?.routeRecording, appRoutes]);

  const recordingCount = routeRecordingRows.filter(r => r.recording).length;

  const routeRecordingColumns: Column<RouteRecordingRow>[] = useMemo(() => [
    { key: 'routeId', header: 'Route', render: (_v: unknown, row: RouteRecordingRow) => <MonoText size="xs">{row.routeId}</MonoText> },
    { key: 'recording', header: 'Recording', width: '100px', render: (_v: unknown, row: RouteRecordingRow) => <Toggle checked={row.recording} onChange={() => { if (editing) updateRouteRecording(row.routeId, !row.recording); }} disabled={!editing} /> },
  ], [editing, routeRecordingDraft]);

  return (
    <>
      {!editing && (
        <div className={styles.editBanner}>
          <span className={styles.editBannerText}>Configuration is read-only. Enter edit mode to make changes.</span>
          <Button size="sm" variant="secondary" onClick={() => setEditing(true)}>Edit</Button>
        </div>
      )}
      {editing && (
        <div className={`${styles.editBanner} ${styles.editBannerActive}`}>
          <span className={styles.editBannerTextWarn}>Editing configuration. Changes are not saved until you click Save.</span>
          <div className={styles.editBannerActions}>
            <Button size="sm" variant="ghost" onClick={handleCancel}>Cancel</Button>
            <Button size="sm" variant="primary" onClick={handleSave}
              loading={updateAgentConfig.isPending || updateContainerConfig.isPending}>Save Configuration</Button>
          </div>
        </div>
      )}

      <Tabs
        tabs={[
          { label: 'Monitoring', value: 'monitoring' },
          { label: 'Resources', value: 'resources' },
          { label: 'Variables', value: 'variables' },
          { label: 'Traces & Taps', value: 'traces' },
          { label: 'Route Recording', value: 'recording' },
        ]}
        active={configTab}
        onChange={(v) => setConfigTab(v as typeof configTab)}
      />

      {configTab === 'variables' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Variables</SectionHeader>
          <EnvEditor value={envVars} onChange={setEnvVars} disabled={!editing} />
        </div>
      )}

      {configTab === 'monitoring' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Monitoring</SectionHeader>
          <div className={styles.configGrid}>
            <span className={styles.configLabel}>Engine Level</span>
            <Select disabled={!editing} value={engineLevel} onChange={(e) => setEngineLevel(e.target.value)}
              options={[{ value: 'NONE', label: 'NONE' }, { value: 'MINIMAL', label: 'MINIMAL' }, { value: 'REGULAR', label: 'REGULAR' }, { value: 'COMPLETE', label: 'COMPLETE' }]} />

            <span className={styles.configLabel}>Payload Capture</span>
            <Select disabled={!editing} value={payloadCapture} onChange={(e) => setPayloadCapture(e.target.value)}
              options={[{ value: 'NONE', label: 'NONE' }, { value: 'INPUT', label: 'INPUT' }, { value: 'OUTPUT', label: 'OUTPUT' }, { value: 'BOTH', label: 'BOTH' }]} />

            <span className={styles.configLabel}>Max Payload Size</span>
            <div className={styles.configInline}>
              <Input disabled={!editing} value={payloadSize} onChange={(e) => setPayloadSize(e.target.value)} className={styles.inputMd} />
              <Select disabled={!editing} value={payloadUnit} onChange={(e) => setPayloadUnit(e.target.value)} className={styles.inputXl}
                options={[{ value: 'bytes', label: 'bytes' }, { value: 'KB', label: 'KB' }, { value: 'MB', label: 'MB' }]} />
            </div>

            <span className={styles.configLabel}>App Log Level</span>
            <Select disabled={!editing} value={appLogLevel} onChange={(e) => setAppLogLevel(e.target.value)}
              options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} />

            <span className={styles.configLabel}>Agent Log Level</span>
            <Select disabled={!editing} value={agentLogLevel} onChange={(e) => setAgentLogLevel(e.target.value)}
              options={['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'].map((l) => ({ value: l, label: l }))} />

            <span className={styles.configLabel}>Metrics</span>
            <div className={styles.configInline}>
              <Toggle checked={metricsEnabled} onChange={() => editing && setMetricsEnabled(!metricsEnabled)} disabled={!editing} />
              <span className={metricsEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{metricsEnabled ? 'Enabled' : 'Disabled'}</span>
              <span className={styles.cellMeta} style={{ marginLeft: 8 }}>Interval</span>
              <Input disabled={!editing} value={metricsInterval} onChange={(e) => setMetricsInterval(e.target.value)} className={styles.inputXs} />
              <span className={styles.cellMeta}>s</span>
            </div>

            <span className={styles.configLabel}>Sampling Rate</span>
            <Input disabled={!editing} value={samplingRate} onChange={(e) => setSamplingRate(e.target.value)} className={styles.inputLg} />

            <span className={styles.configLabel}>Compress Success</span>
            <div className={styles.configInline}>
              <Toggle checked={compressSuccess} onChange={() => editing && setCompressSuccess(!compressSuccess)} disabled={!editing} />
              <span className={compressSuccess ? styles.toggleEnabled : styles.toggleDisabled}>{compressSuccess ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Replay</span>
            <div className={styles.configInline}>
              <Toggle checked={replayEnabled} onChange={() => editing && setReplayEnabled(!replayEnabled)} disabled={!editing} />
              <span className={replayEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{replayEnabled ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Route Control</span>
            <div className={styles.configInline}>
              <Toggle checked={routeControlEnabled} onChange={() => editing && setRouteControlEnabled(!routeControlEnabled)} disabled={!editing} />
              <span className={routeControlEnabled ? styles.toggleEnabled : styles.toggleDisabled}>{routeControlEnabled ? 'Enabled' : 'Disabled'}</span>
            </div>
          </div>
        </div>
      )}

      {configTab === 'traces' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Traces &amp; Taps</SectionHeader>
          <span className={styles.sectionSummary}>{tracedCount} traced &middot; {tapCount} taps</span>
          {tracedTapRows.length > 0
            ? <DataTable<TracedTapRow> columns={tracedTapColumns} data={tracedTapRows} pageSize={20} flush />
            : <EmptyState title="No traces or taps" description="No processor traces or taps configured." />}
        </div>
      )}

      {configTab === 'recording' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Route Recording</SectionHeader>
          <span className={styles.sectionSummary}>{recordingCount} of {routeRecordingRows.length} routes recording</span>
          {routeRecordingRows.length > 0
            ? <DataTable<RouteRecordingRow> columns={routeRecordingColumns} data={routeRecordingRows} pageSize={20} flush />
            : <EmptyState title="No routes" description="No routes found for this application. Routes appear once agents report data." />}
        </div>
      )}

      {configTab === 'resources' && (
        <div className={sectionStyles.section}>
          <SectionHeader>Container Resources</SectionHeader>
          <div className={styles.configGrid}>
            <span className={styles.configLabel}>Runtime Type</span>
            <div>
              <Select disabled={!editing} value={runtimeType} onChange={(e) => setRuntimeType(e.target.value)}
                options={[
                  { value: 'auto', label: 'Auto (detect from JAR)' },
                  { value: 'spring-boot', label: 'Spring Boot' },
                  { value: 'quarkus', label: 'Quarkus' },
                  { value: 'plain-java', label: 'Plain Java' },
                  { value: 'native', label: 'Native' },
                ]} />
              {latestVersion?.detectedRuntimeType && (
                <span className={styles.configHint} style={{ color: 'var(--success)' }}>
                  Detected: {latestVersion.detectedRuntimeType}
                </span>
              )}
              {latestVersion && !latestVersion.detectedRuntimeType && (
                <span className={styles.configHint} style={{ color: 'var(--amber)' }}>
                  Detection failed — select runtime type manually
                </span>
              )}
            </div>

            <span className={styles.configLabel}>Custom Arguments</span>
            <div>
              <Input disabled={!editing} value={customArgs} onChange={(e) => setCustomArgs(e.target.value)}
                placeholder="-Xmx256m -Dfoo=bar" className={styles.inputLg} />
              <span className={styles.configHint}>
                {runtimeType === 'native' ? 'Arguments passed to the native binary' : 'Additional JVM arguments appended to the start command'}
              </span>
            </div>

            <span className={styles.configLabel}>Memory Limit</span>
            <div className={styles.configInline}>
              <Input disabled={!editing} value={memoryLimit} onChange={(e) => setMemoryLimit(e.target.value)} className={styles.inputLg} />
              <span className={styles.cellMeta}>MB</span>
            </div>

            <span className={styles.configLabel}>Memory Reserve</span>
            <div>
              <div className={styles.configInline}>
                <Input disabled value={memoryReserve} onChange={(e) => setMemoryReserve(e.target.value)} placeholder="---" className={styles.inputLg} />
                <span className={styles.cellMeta}>MB</span>
              </div>
              {!isProd && <span className={styles.configHint}>Available in production environments only</span>}
            </div>

            <span className={styles.configLabel}>CPU Request</span>
            <Input disabled={!editing} value={cpuRequest} onChange={(e) => setCpuRequest(e.target.value)} className={styles.inputLg} />

            <span className={styles.configLabel}>CPU Limit</span>
            <div className={styles.configInline}>
              <Input disabled={!editing} value={cpuLimit} onChange={(e) => setCpuLimit(e.target.value)} placeholder="e.g. 1000" className={styles.inputLg} />
              <span className={styles.cellMeta}>millicores</span>
            </div>

            <span className={styles.configLabel}>Exposed Ports</span>
            <div className={styles.portPills}>
              {ports.map((p) => (
                <span key={p} className={styles.portPill}>
                  {p}
                  <button className={styles.portPillDelete} disabled={!editing}
                    onClick={() => editing && setPorts(ports.filter((x) => x !== p))}>&times;</button>
                </span>
              ))}
              <input className={styles.portAddInput} disabled={!editing} placeholder="+ port" value={newPort}
                onChange={(e) => setNewPort(e.target.value)}
                onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addPort(); } }} />
            </div>

            <span className={styles.configLabel}>App Port</span>
            <Input disabled={!editing} value={appPort} onChange={(e) => setAppPort(e.target.value)} className={styles.inputLg} />

            <span className={styles.configLabel}>Replicas</span>
            <Input disabled={!editing} value={replicas} onChange={(e) => setReplicas(e.target.value)} className={styles.inputSm} type="number" />

            <span className={styles.configLabel}>Deploy Strategy</span>
            <Select disabled={!editing} value={deployStrategy} onChange={(e) => setDeployStrategy(e.target.value)}
              options={[{ value: 'blue-green', label: 'Blue/Green' }, { value: 'rolling', label: 'Rolling' }]} />

            <span className={styles.configLabel}>Strip Path Prefix</span>
            <div className={styles.configInline}>
              <Toggle checked={stripPrefix} onChange={() => editing && setStripPrefix(!stripPrefix)} disabled={!editing} />
              <span className={stripPrefix ? styles.toggleEnabled : styles.toggleDisabled}>{stripPrefix ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>SSL Offloading</span>
            <div className={styles.configInline}>
              <Toggle checked={sslOffloading} onChange={() => editing && setSslOffloading(!sslOffloading)} disabled={!editing} />
              <span className={sslOffloading ? styles.toggleEnabled : styles.toggleDisabled}>{sslOffloading ? 'Enabled' : 'Disabled'}</span>
            </div>

            <span className={styles.configLabel}>Extra Networks</span>
            <div>
              <div className={styles.portPills}>
                {extraNetworks.map((n) => (
                  <span key={n} className={styles.portPill}>
                    {n}
                    <button className={styles.portPillDelete} disabled={!editing}
                      onClick={() => editing && setExtraNetworks(extraNetworks.filter((x) => x !== n))}>&times;</button>
                  </span>
                ))}
                <input className={styles.portAddInput} disabled={!editing} placeholder="+ network" value={newNetwork}
                  onChange={(e) => setNewNetwork(e.target.value)}
                  onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); const v = newNetwork.trim(); if (v && !extraNetworks.includes(v)) { setExtraNetworks([...extraNetworks, v]); setNewNetwork(''); } } }} />
              </div>
              <span className={styles.configHint}>Additional Docker networks to join (e.g., monitoring, prometheus)</span>
            </div>
          </div>
        </div>
      )}
    </>
  );
}
