import 'package:flutter/material.dart';

import '../data/error_presentation.dart';
import '../data/chain_log.dart';
import '../data/hub.dart';
import '../data/system_actions.dart';
import '../l10n/app_localizations.dart';
import '../theme/theme.dart';
import '../theme/tokens.dart';
import '../widgets/widgets.dart';
import 'welcome.dart' show showFaiDoc;

class DoctorPage extends StatefulWidget {
  const DoctorPage({super.key});

  @override
  State<DoctorPage> createState() => _DoctorPageState();
}

class _DoctorPageState extends State<DoctorPage> {
  late Future<DoctorSnapshot> _future;

  @override
  void initState() {
    super.initState();
    _future = HubService.instance.doctor();
  }

  void _refresh() => setState(() {
    _future = HubService.instance.doctor();
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).scaffoldBackgroundColor,
      appBar: AppBar(
        title: Text(AppLocalizations.of(context)!.doctorTitle),
        actions: [
          IconButton(
            icon: const Icon(Icons.help_outline, size: 18),
            tooltip: AppLocalizations.of(context)!.helpTooltip,
            onPressed: () => showFaiDoc(context, 'architecture'),
          ),
          IconButton(
            icon: const Icon(Icons.refresh, size: 18),
            tooltip: AppLocalizations.of(context)!.doctorRecheckTooltip,
            onPressed: _refresh,
          ),
          const SizedBox(width: ChainSpace.sm),
        ],
      ),
      body: FutureBuilder<DoctorSnapshot>(
        future: _future,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            final l = AppLocalizations.of(context)!;
            return ChainEmptyState(
              icon: Icons.cloud_off_outlined,
              iconColor: Theme.of(context).colorScheme.error,
              title: l.hubUnreachable,
              hint: l.hubUnreachableHint,
              action: FilledButton.tonal(
                onPressed: _refresh,
                child: Text(l.buttonRetry),
              ),
            );
          }
          final s = snapshot.data!;
          final showUpdate =
              s.update.updateAvailable ||
              (!s.update.manifestReachable && s.update.localVersion.isNotEmpty);
          return ListView(
            padding: const EdgeInsets.all(ChainSpace.xl),
            children: [
              if (showUpdate) _UpdateBanner(status: s.update),
              if (showUpdate) const SizedBox(height: ChainSpace.lg),
              _SummaryStrip(snapshot: s),
              const SizedBox(height: ChainSpace.xl),
              _Section(
                title: AppLocalizations.of(context)!.doctorEventLogSection,
                child: _EventLogPanel(snapshot: s, onRefresh: _refresh),
              ),
              const SizedBox(height: ChainSpace.lg),
              _Section(
                title: AppLocalizations.of(
                  context,
                )!.doctorModulesApprovalsSection,
                child: _ModulesPanel(snapshot: s),
              ),
              const SizedBox(height: ChainSpace.lg),
              _Section(
                title: AppLocalizations.of(context)!.doctorHostServicesSection,
                child: _ServicesPanel(snapshot: s),
              ),
              const SizedBox(height: ChainSpace.lg),
              _Section(
                title: AppLocalizations.of(context)!.doctorDaemonFilesSection,
                child: _DaemonPathsPanel(paths: s.paths),
              ),
              const SizedBox(height: ChainSpace.lg),
              _Section(
                title: AppLocalizations.of(context)!.doctorDaemonControlSection,
                child: _DaemonActionsCard(),
              ),
            ],
          );
        },
      ),
    );
  }
}

class _Section extends StatelessWidget {
  final String title;
  final Widget child;

  const _Section({required this.title, required this.child});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.only(
            left: ChainSpace.xs,
            bottom: ChainSpace.sm,
          ),
          child: Text(
            title.toUpperCase(),
            style: theme.textTheme.labelSmall?.copyWith(
              color: theme.colorScheme.onSurfaceVariant,
              letterSpacing: 0.6,
              fontSize: 10,
            ),
          ),
        ),
        child,
      ],
    );
  }
}

class _SummaryStrip extends StatelessWidget {
  final DoctorSnapshot snapshot;

  const _SummaryStrip({required this.snapshot});

  @override
  Widget build(BuildContext context) {
    final l = AppLocalizations.of(context)!;
    return Row(
      children: [
        _StatTile(
          label: l.doctorSummaryModules,
          value: snapshot.moduleCount.toString(),
          subtitle: l.doctorSummaryCapabilities(snapshot.capabilityCount),
          icon: Icons.extension_outlined,
        ),
        const SizedBox(width: ChainSpace.md),
        _StatTile(
          label: l.doctorSummaryApprovals,
          value: snapshot.pendingApprovals.toString(),
          subtitle: l.doctorSummaryPending,
          icon: Icons.inbox_outlined,
          tone: snapshot.pendingApprovals > 0
              ? ChainPillTone.warning
              : ChainPillTone.neutral,
        ),
        const SizedBox(width: ChainSpace.md),
        _StatTile(
          label: l.doctorSummaryAudit,
          value: snapshot.chainHealthy ? '✓' : '⚠',
          subtitle: l.doctorSummaryChain(
            snapshot.eventChainVerified,
            snapshot.eventChainTotal,
          ),
          icon: Icons.shield_outlined,
          tone: snapshot.chainHealthy
              ? ChainPillTone.success
              : ChainPillTone.danger,
        ),
        const SizedBox(width: ChainSpace.md),
        _StatTile(
          label: l.doctorSummaryServices,
          value: snapshot.services.length.toString(),
          subtitle: l.doctorSummaryDeclared,
          icon: Icons.dns_outlined,
        ),
      ],
    );
  }
}

class _StatTile extends StatelessWidget {
  final String label;
  final String value;
  final String subtitle;
  final IconData icon;
  final ChainPillTone tone;

  const _StatTile({
    required this.label,
    required this.value,
    required this.subtitle,
    required this.icon,
    this.tone = ChainPillTone.neutral,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final accentColor = switch (tone) {
      ChainPillTone.success => ChainColors.success,
      ChainPillTone.warning => ChainColors.warning,
      ChainPillTone.danger => theme.colorScheme.error,
      _ => theme.colorScheme.primary,
    };
    return Expanded(
      child: ChainCard(
        accentLeft: accentColor,
        padding: const EdgeInsets.all(ChainSpace.lg),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(icon, size: 14, color: theme.colorScheme.onSurfaceVariant),
                const SizedBox(width: ChainSpace.xs),
                Flexible(
                  child: Text(
                    label.toUpperCase(),
                    overflow: TextOverflow.ellipsis,
                    style: theme.textTheme.labelSmall?.copyWith(
                      color: theme.colorScheme.onSurfaceVariant,
                      letterSpacing: 0.5,
                      fontSize: 10,
                    ),
                  ),
                ),
              ],
            ),
            const SizedBox(height: ChainSpace.sm),
            Text(
              value,
              style: theme.textTheme.displaySmall?.copyWith(
                color: accentColor,
                fontWeight: FontWeight.w700,
              ),
            ),
            const SizedBox(height: 2),
            Text(
              subtitle,
              style: theme.textTheme.bodySmall?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _EventLogPanel extends StatelessWidget {
  final DoctorSnapshot snapshot;

  /// Re-runs the doctor() call which re-verifies the chain.
  /// Wired to a "Verify now" button so operators can re-check
  /// after import / restore without restarting the daemon.
  final VoidCallback onRefresh;

  const _EventLogPanel({required this.snapshot, required this.onRefresh});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final healthy = snapshot.chainHealthy;
    final l = AppLocalizations.of(context)!;
    return ChainCard(
      child: Row(
        children: [
          Icon(
            healthy ? Icons.verified_outlined : Icons.gpp_bad_outlined,
            size: 24,
            color: healthy ? ChainColors.success : theme.colorScheme.error,
          ),
          const SizedBox(width: ChainSpace.lg),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  healthy ? l.doctorChainIntact : l.doctorChainTampered,
                  style: theme.textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.w600,
                  ),
                ),
                const SizedBox(height: 2),
                Text(
                  healthy
                      ? l.doctorChainIntactDetail(snapshot.eventChainTotal)
                      : l.doctorChainTamperedDetail(
                          snapshot.eventChainVerified,
                          snapshot.eventChainTotal,
                          snapshot.eventChainTamperedAt ?? '',
                        ),
                  style: theme.textTheme.bodySmall?.copyWith(
                    color: theme.colorScheme.onSurfaceVariant,
                  ),
                ),
              ],
            ),
          ),
          OutlinedButton.icon(
            onPressed: onRefresh,
            icon: const Icon(Icons.fact_check_outlined, size: 16),
            label: Text(l.doctorVerifyNow),
          ),
          const SizedBox(width: ChainSpace.sm),
          ChainPill(
            label: healthy ? l.doctorChainPillOk : l.doctorChainPillTamper,
            tone: healthy ? ChainPillTone.success : ChainPillTone.danger,
          ),
        ],
      ),
    );
  }
}

/// Lists the daemon's filesystem paths with one "Open" button
/// per row. Windows operators who never touch a shell still
/// need a way to inspect the audit DB or edit the operator
/// config; this panel is that escape hatch.
class _DaemonPathsPanel extends StatelessWidget {
  final DaemonPathsSnapshot paths;

  const _DaemonPathsPanel({required this.paths});

  @override
  Widget build(BuildContext context) {
    final l = AppLocalizations.of(context)!;
    // Per-row layout: (label, path, icon, isDirectory). The
    // isDirectory bit drives `openOrReveal` so binary files
    // (the SQLite audit DB, the PID file) reveal in Finder /
    // Explorer instead of failing the OS "open" handler.
    final entries = <(String, String, IconData, bool)>[
      (l.doctorPathLog, paths.logPath, Icons.description_outlined, false),
      (
        l.doctorPathStudioErrors,
        ChainLog.instance.path,
        Icons.report_problem_outlined,
        false,
      ),
      (l.doctorPathConfig, paths.configPath, Icons.settings_outlined, false),
      (l.doctorPathDb, paths.dbPath, Icons.storage_outlined, false),
      (l.doctorPathModules, paths.modulesDir, Icons.extension_outlined, true),
      (l.doctorPathFlows, paths.flowsDir, Icons.account_tree_outlined, true),
      (l.doctorPathPid, paths.pidPath, Icons.fingerprint, false),
    ].where((e) => e.$2.isNotEmpty).toList();

    if (entries.isEmpty) {
      return ChainCard(
        child: Text(
          l.doctorPathsEmpty,
          style: Theme.of(context).textTheme.bodySmall,
        ),
      );
    }

    return ChainCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (final e in entries)
            _PathRow(label: e.$1, path: e.$2, icon: e.$3, isDirectory: e.$4),
        ],
      ),
    );
  }
}

class _PathRow extends StatelessWidget {
  final String label;
  final String path;
  final IconData icon;
  final bool isDirectory;

  const _PathRow({
    required this.label,
    required this.path,
    required this.icon,
    required this.isDirectory,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    final lower = path.toLowerCase();
    final isLog = !isDirectory && lower.endsWith('.log');
    // Text config files get an in-Studio viewer too (read top-down,
    // no log colouring) so the operator can read config.yaml
    // without leaving Studio or hunting for an external editor.
    final isConfig = !isDirectory &&
        (lower.endsWith('.yaml') ||
            lower.endsWith('.yml') ||
            lower.endsWith('.toml'));
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          Icon(icon, size: 16, color: theme.colorScheme.onSurfaceVariant),
          const SizedBox(width: ChainSpace.sm),
          SizedBox(
            width: 90,
            child: Text(
              label,
              style: theme.textTheme.labelSmall?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
          ),
          Expanded(
            child: SelectableText(
              path,
              style: ChainTheme.mono(
                size: 11,
                color: theme.colorScheme.onSurface,
              ),
            ),
          ),
          const SizedBox(width: ChainSpace.sm),
          if (isLog || isConfig) ...[
            OutlinedButton.icon(
              onPressed: () => isConfig
                  ? showFaiConfigViewer(context, path: path, title: label)
                  : showFaiLogViewer(context, path: path, title: label),
              icon: const Icon(Icons.visibility_outlined, size: 14),
              label: Text(l.buttonView),
              style: OutlinedButton.styleFrom(
                visualDensity: VisualDensity.compact,
              ),
            ),
            const SizedBox(width: ChainSpace.xs),
          ],
          OutlinedButton.icon(
            onPressed: () async {
              final r = await SystemActions.openOrReveal(
                path,
                isDirectory: isDirectory,
              );
              if (!context.mounted) return;
              if (!r.ok) {
                showFaiErrorSnack(
                  context,
                  'doctor.open-path',
                  r.stderr.isEmpty ? 'open failed' : r.stderr,
                );
              }
            },
            icon: Icon(
              isDirectory ? Icons.folder_open : Icons.open_in_new,
              size: 14,
            ),
            label: Text(l.buttonOpen),
            style: OutlinedButton.styleFrom(
              visualDensity: VisualDensity.compact,
            ),
          ),
        ],
      ),
    );
  }
}

/// Daemon control affordances: Restart, Apply update, Stop.
/// Each shells out to the `fai` binary (resolved via PATH or
/// `~/.chain/bin/chain`). Studio is the GUI shell; the binary owns
/// the supervisor logic so the daemon and CLI stay in lockstep.
class _DaemonActionsCard extends StatefulWidget {
  @override
  State<_DaemonActionsCard> createState() => _DaemonActionsCardState();
}

class _DaemonActionsCardState extends State<_DaemonActionsCard> {
  bool _busy = false;
  String? _output;

  /// True when the last daemon action failed because no `fai`
  /// binary could be located. Swaps the raw output line for the
  /// actionable [ChainBinaryRecovery] block.
  bool _binaryMissing = false;
  ChannelStatusSnapshot? _channels;

  @override
  void initState() {
    super.initState();
    _refreshStatus();
  }

  Future<void> _refreshStatus() async {
    try {
      final snap = await HubService.instance.channelStatus();
      if (!mounted) return;
      setState(() => _channels = snap);
    } catch (_) {
      // Best-effort; the status pill simply renders "unknown".
    }
  }

  Future<void> _run(
    String label,
    Future<({bool ok, String stdout, String stderr})> Function() action,
  ) async {
    setState(() {
      _busy = true;
      _output = null;
    });
    final r = await action();
    if (!mounted) return;
    final l = AppLocalizations.of(context)!;
    final binaryMissing = !r.ok && r.stderr.trim() == kFaiBinaryNotFound;
    setState(() {
      _busy = false;
      _binaryMissing = binaryMissing;
      // When the binary is missing we render the actionable
      // recovery block instead of a raw output line — the
      // sentinel is not a user-facing string.
      _output = binaryMissing
          ? null
          : (r.ok
                    ? '${l.daemonActionResultOk(label)}\n${r.stdout}'
                    : '${l.daemonActionResultFailed(label)}\n${r.stderr.isEmpty ? r.stdout : r.stderr}')
                .trim();
    });
    // Refresh the status pill — restart / stop / start all
    // change the running flag.
    await _refreshStatus();
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    ChannelInfo? active;
    if (_channels != null) {
      for (final c in _channels!.channels) {
        if (c.name == _channels!.active) {
          active = c;
          break;
        }
      }
    }
    return ChainCard(
      // Row + Expanded forces the card to take the full width
      // its parent offers. Without this, the card hugs the
      // intrinsic width of the longest button row and ends up
      // visibly narrower than its siblings.
      child: SizedBox(
        width: double.infinity,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  active?.running == true
                      ? Icons.check_circle_outline
                      : Icons.power_off_outlined,
                  size: 18,
                  color: active?.running == true
                      ? ChainColors.success
                      : theme.colorScheme.onSurfaceVariant,
                ),
                const SizedBox(width: ChainSpace.sm),
                Expanded(
                  child: Text(
                    active == null
                        ? l.doctorStatusUnknown
                        : active.running
                        ? l.doctorRunningOn(active.name, active.endpoint)
                        : l.doctorStoppedOn(active.name),
                    style: theme.textTheme.bodyMedium?.copyWith(
                      fontWeight: FontWeight.w500,
                    ),
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
                if (active != null) ...[
                  const SizedBox(width: ChainSpace.sm),
                  ChainPill(
                    label: active.running
                        ? l.doctorPillRunning
                        : l.doctorPillStopped,
                    tone: active.running
                        ? ChainPillTone.success
                        : ChainPillTone.neutral,
                  ),
                ],
              ],
            ),
            const SizedBox(height: ChainSpace.sm),
            Text(
              l.doctorDaemonControlHint,
              style: theme.textTheme.bodySmall?.copyWith(
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: ChainSpace.md),
            Wrap(
              spacing: ChainSpace.sm,
              runSpacing: ChainSpace.sm,
              children: [
                FilledButton.tonalIcon(
                  onPressed: _busy
                      ? null
                      : () => _run(
                          l.daemonActionRestart,
                          () => SystemActions.chainDaemon(['restart']),
                        ),
                  icon: const Icon(Icons.restart_alt, size: 16),
                  label: Text(l.doctorRestart),
                ),
                if (active?.running != true)
                  FilledButton.icon(
                    onPressed: _busy
                        ? null
                        : () => _run(
                            l.daemonActionStart,
                            () => SystemActions.chainDaemon(['start']),
                          ),
                    icon: const Icon(Icons.play_arrow, size: 16),
                    label: Text(l.doctorStart),
                  ),
                OutlinedButton.icon(
                  onPressed: _busy
                      ? null
                      : () => _run(
                          l.daemonActionStop,
                          () => SystemActions.chainDaemon(['stop']),
                        ),
                  icon: const Icon(Icons.stop_circle_outlined, size: 16),
                  label: Text(l.doctorStop),
                ),
                OutlinedButton.icon(
                  onPressed: _busy
                      ? null
                      : () => _run(
                          l.daemonActionStatus,
                          () => SystemActions.chainDaemon(['status']),
                        ),
                  icon: const Icon(Icons.health_and_safety_outlined, size: 16),
                  label: Text(l.doctorStatusAction),
                ),
              ],
            ),
            if (_busy) ...[
              const SizedBox(height: ChainSpace.md),
              Row(
                children: [
                  const SizedBox(
                    width: 14,
                    height: 14,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  ),
                  const SizedBox(width: ChainSpace.sm),
                  Text(
                    l.doctorDaemonControlWorking,
                    style: theme.textTheme.bodySmall?.copyWith(
                      color: theme.colorScheme.onSurfaceVariant,
                    ),
                  ),
                ],
              ),
            ],
            if (_binaryMissing) ...[
              const SizedBox(height: ChainSpace.md),
              ChainBinaryRecovery(
                onLocated: () => _run(
                  l.daemonActionStatus,
                  () => SystemActions.chainDaemon(['status']),
                ),
              ),
            ],
            if (_output != null) ...[
              const SizedBox(height: ChainSpace.md),
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(ChainSpace.sm),
                decoration: BoxDecoration(
                  color: theme.colorScheme.surfaceContainerHigh,
                  borderRadius: BorderRadius.circular(ChainRadius.sm),
                  border: Border.all(color: theme.colorScheme.outlineVariant),
                ),
                child: SelectableText(
                  _output!,
                  style: ChainTheme.mono(
                    size: 11,
                    color: theme.colorScheme.onSurface,
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }
}

/// Format the per-source-kind capability count map as a
/// human-readable line like "6 bundle · 3 mcp · 1 system".
/// Sorted alphabetically for deterministic rendering.
String _sourceBreakdown(Map<String, int> bySource) {
  final entries = bySource.entries.toList()
    ..sort((a, b) => a.key.compareTo(b.key));
  return entries.map((e) => '${e.value} ${e.key}').join(' · ');
}

class _ModulesPanel extends StatelessWidget {
  final DoctorSnapshot snapshot;

  const _ModulesPanel({required this.snapshot});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    return ChainCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(
                Icons.extension_outlined,
                size: 18,
                color: theme.colorScheme.onSurfaceVariant,
              ),
              const SizedBox(width: ChainSpace.sm),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      l.doctorModulesPanelSummary(
                        snapshot.moduleCount,
                        snapshot.capabilityCount,
                      ),
                      style: theme.textTheme.bodyMedium,
                    ),
                    if (snapshot.capabilitiesBySource.isNotEmpty) ...[
                      const SizedBox(height: 2),
                      Text(
                        _sourceBreakdown(snapshot.capabilitiesBySource),
                        style: theme.textTheme.bodySmall?.copyWith(
                          color: theme.colorScheme.onSurfaceVariant,
                        ),
                      ),
                    ],
                  ],
                ),
              ),
              ChainPill(
                label: snapshot.moduleCount > 0
                    ? l.doctorPillLoaded
                    : l.doctorPillEmpty,
                tone: snapshot.moduleCount > 0
                    ? ChainPillTone.success
                    : ChainPillTone.neutral,
              ),
            ],
          ),
          const Divider(height: ChainSpace.xl),
          Row(
            children: [
              Icon(
                Icons.inbox_outlined,
                size: 18,
                color: theme.colorScheme.onSurfaceVariant,
              ),
              const SizedBox(width: ChainSpace.sm),
              Text(
                snapshot.pendingApprovals == 0
                    ? l.doctorApprovalsNone
                    : l.doctorApprovalsCount(snapshot.pendingApprovals),
                style: theme.textTheme.bodyMedium,
              ),
              const Spacer(),
              if (snapshot.pendingApprovals > 0)
                ChainPill(
                  label: l.doctorApprovalsAttentionPill,
                  tone: ChainPillTone.warning,
                ),
            ],
          ),
        ],
      ),
    );
  }
}

class _ServicesPanel extends StatelessWidget {
  final DoctorSnapshot snapshot;

  const _ServicesPanel({required this.snapshot});

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    if (snapshot.services.isEmpty) {
      // Wrap on narrow windows so the mono-spaced hint
      // (`add to ~/.chain/config.yaml under services:`) does not
      // overflow horizontally past the card's right edge.
      return ChainCard(
        child: Wrap(
          spacing: ChainSpace.sm,
          runSpacing: ChainSpace.xs,
          crossAxisAlignment: WrapCrossAlignment.center,
          children: [
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(
                  Icons.dns_outlined,
                  size: 18,
                  color: theme.colorScheme.onSurfaceVariant,
                ),
                const SizedBox(width: ChainSpace.sm),
                Text(
                  l.doctorServicesEmpty,
                  style: theme.textTheme.bodyMedium?.copyWith(
                    color: theme.colorScheme.onSurfaceVariant,
                  ),
                ),
              ],
            ),
            Text(
              l.doctorServicesEmptyHint,
              style: ChainTheme.mono(
                size: 11,
                color: theme.colorScheme.onSurfaceVariant,
              ),
            ),
          ],
        ),
      );
    }
    return ChainCard(
      padding: EdgeInsets.zero,
      child: Column(
        children: [
          for (var i = 0; i < snapshot.services.length; i++) ...[
            if (i > 0)
              Divider(height: 1, color: theme.colorScheme.outlineVariant),
            Padding(
              padding: const EdgeInsets.all(ChainSpace.lg),
              child: Row(
                children: [
                  Icon(
                    Icons.dns_outlined,
                    size: 16,
                    color: theme.colorScheme.primary,
                  ),
                  const SizedBox(width: ChainSpace.sm),
                  Text(
                    snapshot.services[i].name,
                    style: theme.textTheme.titleSmall?.copyWith(
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  const SizedBox(width: ChainSpace.md),
                  Text(
                    snapshot.services[i].endpoint,
                    style: ChainTheme.mono(
                      size: 11,
                      color: theme.colorScheme.onSurfaceVariant,
                    ),
                  ),
                  const Spacer(),
                  for (final tag in snapshot.services[i].tags) ...[
                    ChainPill(label: tag, tone: ChainPillTone.neutral),
                    const SizedBox(width: ChainSpace.xs),
                  ],
                ],
              ),
            ),
          ],
        ],
      ),
    );
  }
}

class _UpdateBanner extends StatefulWidget {
  final UpdateStatus status;

  const _UpdateBanner({required this.status});

  @override
  State<_UpdateBanner> createState() => _UpdateBannerState();
}

class _UpdateBannerState extends State<_UpdateBanner> {
  bool _applying = false;
  String? _applyOutput;

  Future<void> _applyUpdate() async {
    setState(() {
      _applying = true;
      _applyOutput = null;
    });
    final r = await SystemActions.chainUpdateApply(widget.status.channel);
    if (!mounted) return;
    setState(() {
      _applying = false;
      _applyOutput = r.ok
          ? AppLocalizations.of(context)!.doctorApplyDone
          : (r.stderr.isEmpty ? r.stdout : r.stderr).trim();
    });
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    final status = widget.status;
    final available = status.updateAvailable;
    final iconData = available
        ? Icons.system_update_alt_outlined
        : Icons.cloud_off_outlined;
    final iconColor = available
        ? theme.colorScheme.primary
        : theme.colorScheme.onSurfaceVariant;
    final title = available
        ? l.doctorUpdateAvailable(status.latestVersion)
        : l.doctorUpdateUnreachable;
    final body = available
        ? l.doctorUpdateBody(status.channel, status.latestVersion)
        : (status.reason ?? l.doctorUpdateUnreachableBody(status.channel));
    return ChainCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Icon(iconData, size: 20, color: iconColor),
              const SizedBox(width: ChainSpace.md),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: theme.textTheme.titleSmall?.copyWith(
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 2),
                    Text(
                      body,
                      style: ChainTheme.mono(
                        size: 11,
                        color: theme.colorScheme.onSurfaceVariant,
                      ),
                    ),
                    if (available && status.releaseNotesUrl != null) ...[
                      const SizedBox(height: 4),
                      Text(
                        l.doctorReleaseNotes(status.releaseNotesUrl!),
                        style: ChainTheme.mono(
                          size: 11,
                          color: theme.colorScheme.primary,
                        ),
                      ),
                    ],
                  ],
                ),
              ),
              if (available)
                FilledButton.icon(
                  onPressed: _applying ? null : _applyUpdate,
                  icon: _applying
                      ? const SizedBox(
                          width: 14,
                          height: 14,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Icon(Icons.system_update_alt, size: 16),
                  label: Text(
                    _applying ? l.doctorApplying : l.doctorApplyUpdate,
                  ),
                ),
              const SizedBox(width: ChainSpace.sm),
              ChainPill(
                label: available ? l.doctorPillNew : l.doctorPillOffline,
                tone: available ? ChainPillTone.success : ChainPillTone.neutral,
              ),
            ],
          ),
          if (_applyOutput != null) ...[
            const SizedBox(height: ChainSpace.md),
            ChainErrorBox(text: _applyOutput!, maxHeight: 240),
          ],
        ],
      ),
    );
  }
}
