import 'package:flutter/material.dart';
import 'oto_artifact_record_row.dart';
import 'oto_console_contract.dart';

class OtoExecutionsSurface extends StatelessWidget {
  final OtoSurfaceSnapshot<List<OtoExecutionViewModel>> snapshot;
  final String? expandedExecutionID;
  final Map<String, OtoActionViewState>? actionStates;
  final OtoSurfaceSnapshot<List<OtoLogEntryViewModel>>? logsSnapshot;
  final OtoSurfaceSnapshot<List<OtoArtifactViewModel>>? artifactsSnapshot;
  final ValueChanged<String?>? onExpandExecution;
  final VoidCallback? onViewArtifactsSection;
  final ValueChanged<OtoExecutionCancelDraft>? onCancelExecution;
  final ValueChanged<OtoExecutionReportDraft>? onReportExecution;
  final ValueChanged<OtoExecutionLogDraft>? onAppendLog;
  final ValueChanged<OtoExecutionArtifactDraft>? onAppendArtifact;
  final OtoConsoleThemeAdapter? themeAdapter;

  const OtoExecutionsSurface({
    super.key,
    required this.snapshot,
    this.expandedExecutionID,
    this.actionStates,
    this.logsSnapshot,
    this.artifactsSnapshot,
    this.onExpandExecution,
    this.onViewArtifactsSection,
    this.onCancelExecution,
    this.onReportExecution,
    this.onAppendLog,
    this.onAppendArtifact,
    this.themeAdapter,
  });

  @override
  Widget build(BuildContext context) {
    final theme = themeAdapter ?? const OtoConsoleThemeAdapter();
    return Container(
      color: theme.backgroundColor,
      padding: const EdgeInsets.all(24),
      child: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 820),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Icon(
                    Icons.play_circle_outline,
                    color: theme.primaryColor,
                    size: 24,
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    child: Text(
                      'Executions',
                      style: TextStyle(
                        color: theme.textColor,
                        fontSize: 24,
                        fontWeight: FontWeight.w800,
                      ),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 20),
              Expanded(child: _buildContent(context, theme)),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildContent(BuildContext context, OtoConsoleThemeAdapter theme) {
    switch (snapshot.state) {
      case OtoSurfaceLoadState.loading:
        return const Center(child: CircularProgressIndicator());
      case OtoSurfaceLoadState.empty:
        return _buildEmptyState(theme);
      case OtoSurfaceLoadState.error:
        return _buildErrorState(theme);
      case OtoSurfaceLoadState.data:
        final executions = snapshot.data ?? [];
        if (executions.isEmpty) {
          return _buildEmptyState(theme);
        }
        return _buildExecutionsList(context, theme, executions);
    }
  }

  Widget _buildEmptyState(OtoConsoleThemeAdapter theme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Container(
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            color: theme.railBackgroundColor,
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: theme.borderColor),
          ),
          child: Row(
            children: [
              Icon(
                Icons.play_circle_outline,
                color: theme.primaryColor,
                size: 32,
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'No executions',
                      style: TextStyle(
                        color: theme.textColor,
                        fontSize: 18,
                        fontWeight: FontWeight.w800,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      'Execution history is empty.',
                      style: TextStyle(color: theme.textSecondaryColor),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildErrorState(OtoConsoleThemeAdapter theme) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Container(
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            color: Colors.red.withValues(alpha: 0.1),
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: Colors.red.withValues(alpha: 0.5)),
          ),
          child: Row(
            children: [
              const Icon(Icons.error_outline, color: Colors.red, size: 32),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Error loading executions',
                      style: TextStyle(
                        color: theme.textColor,
                        fontSize: 18,
                        fontWeight: FontWeight.w800,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      snapshot.errorMessage ?? 'An unknown error occurred.',
                      style: TextStyle(color: theme.textSecondaryColor),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildExecutionsList(
    BuildContext context,
    OtoConsoleThemeAdapter theme,
    List<OtoExecutionViewModel> executions,
  ) {
    final selected = _selectedExecution(executions);

    final list = ListView.separated(
      itemCount: executions.length,
      separatorBuilder: (context, index) => const SizedBox(height: 12),
      itemBuilder: (context, index) =>
          _buildExecutionRow(theme, executions[index]),
    );

    // Jenkins Build History + build page relationship: a run list on top and a
    // dedicated selected-run detail below, instead of inline expansion.
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Expanded(child: list),
        const SizedBox(height: 16),
        if (selected == null)
          _RunDetailPlaceholder(theme: theme)
        else
          Expanded(
            child: SingleChildScrollView(
              child: _buildExecutionDetail(context, theme, selected),
            ),
          ),
      ],
    );
  }

  OtoExecutionViewModel? _selectedExecution(
    List<OtoExecutionViewModel> executions,
  ) {
    if (expandedExecutionID == null) {
      return null;
    }
    for (final exec in executions) {
      if (exec.executionID == expandedExecutionID) {
        return exec;
      }
    }
    return null;
  }

  Widget _buildExecutionRow(
    OtoConsoleThemeAdapter theme,
    OtoExecutionViewModel exec,
  ) {
    final isSelected = expandedExecutionID == exec.executionID;

    return Container(
      decoration: BoxDecoration(
        color: theme.railBackgroundColor,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(
          color: isSelected ? theme.primaryColor : theme.borderColor,
        ),
      ),
      child: InkWell(
        onTap: () {
          if (isSelected) {
            onExpandExecution?.call(null);
          } else {
            onExpandExecution?.call(exec.executionID);
          }
        },
        borderRadius: BorderRadius.circular(8),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Expanded(
                    child: Text(
                      'Exec ID: ${exec.executionID}',
                      style: TextStyle(
                        color: theme.textColor,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                  const SizedBox(width: 8),
                  _StatusBadge(state: exec.state, theme: theme),
                ],
              ),
              const SizedBox(height: 8),
              Text(
                'Job ID: ${exec.jobID}',
                style: TextStyle(color: theme.textSecondaryColor, fontSize: 13),
              ),
              if (exec.runnerID.isNotEmpty) ...[
                const SizedBox(height: 4),
                Text(
                  'Runner ID: ${exec.runnerID}',
                  style: TextStyle(
                    color: theme.textSecondaryColor,
                    fontSize: 13,
                  ),
                ),
              ],
              const SizedBox(height: 8),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Expanded(
                    child: Text(
                      'Created: ${exec.createdAt}',
                      style: TextStyle(
                        color: theme.textSecondaryColor,
                        fontSize: 12,
                      ),
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                  Icon(
                    isSelected ? Icons.expand_less : Icons.expand_more,
                    color: theme.textColor,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildExecutionDetail(
    BuildContext context,
    OtoConsoleThemeAdapter theme,
    OtoExecutionViewModel exec,
  ) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: theme.railBackgroundColor,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: theme.borderColor),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          _RunTaskMenu(theme: theme),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _buildRunHeader(theme, exec),
                const SizedBox(height: 16),
                _buildActionsSection(context, theme, exec),
                const SizedBox(height: 16),
                _DetailPane(
                  title: 'Pipeline Overview',
                  theme: theme,
                  child: _PipelineStageGraph(theme: theme, exec: exec),
                ),
                const SizedBox(height: 16),
                _DetailPane(
                  title: 'Console Output',
                  theme: theme,
                  child: _LogPreviewBox(
                    theme: theme,
                    child: _buildLogsContent(theme),
                  ),
                ),
                const SizedBox(height: 16),
                _DetailPane(
                  title: 'Artifacts',
                  theme: theme,
                  action: onViewArtifactsSection == null
                      ? null
                      : TextButton.icon(
                          onPressed: onViewArtifactsSection,
                          icon: const Icon(Icons.arrow_forward, size: 14),
                          label: const Text('View Artifacts Tab'),
                          style: TextButton.styleFrom(
                            foregroundColor: theme.primaryColor,
                            padding: EdgeInsets.zero,
                            visualDensity: VisualDensity.compact,
                          ),
                        ),
                  child: _buildArtifactsContent(theme),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildRunHeader(
    OtoConsoleThemeAdapter theme,
    OtoExecutionViewModel exec,
  ) {
    final meta = exec.runnerID.isEmpty
        ? 'Job ${exec.jobID}'
        : 'Job ${exec.jobID} · Runner ${exec.runnerID}';
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: Text(
                'Run ${exec.executionID}',
                style: TextStyle(
                  color: theme.textColor,
                  fontSize: 18,
                  fontWeight: FontWeight.w800,
                ),
                overflow: TextOverflow.ellipsis,
              ),
            ),
            const SizedBox(width: 8),
            _StatusBadge(state: exec.state, theme: theme),
          ],
        ),
        const SizedBox(height: 4),
        Text(
          meta,
          style: TextStyle(color: theme.textSecondaryColor, fontSize: 12),
          overflow: TextOverflow.ellipsis,
        ),
      ],
    );
  }

  Widget _buildActionsSection(
    BuildContext ctx,
    OtoConsoleThemeAdapter theme,
    OtoExecutionViewModel exec,
  ) {
    final actionState = actionStates?[exec.executionID];
    final canAct =
        exec.runnerID.isNotEmpty &&
        !exec.state.toLowerCase().contains('cancel');
    final isSubmitting = actionState?.isSubmitting ?? false;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Actions',
          style: TextStyle(
            color: theme.textColor,
            fontSize: 14,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        _ExecutionActionToolbar(
          destructive: [
            _ActionButton(
              label: 'Cancel',
              icon: Icons.stop_circle,
              color: Colors.orange,
              enabled: canAct && !isSubmitting,
              state: actionState,
              type: ActionButtonType.cancel,
              themeAdapter: theme,
              onTap: canAct && !isSubmitting
                  ? () {
                      _showCancelConfirmation(ctx, exec);
                    }
                  : null,
            ),
          ],
          reporting: [
            _ActionButton(
              label: 'Report',
              icon: Icons.check_circle,
              color: theme.primaryColor,
              enabled: canAct && !isSubmitting,
              state: actionState,
              type: ActionButtonType.report,
              themeAdapter: theme,
              onTap: canAct && !isSubmitting
                  ? () {
                      onReportExecution?.call(
                        OtoExecutionReportDraft(
                          executionID: exec.executionID,
                          runnerID: exec.runnerID,
                          success: true,
                        ),
                      );
                    }
                  : null,
            ),
          ],
          append: [
            _ActionButton(
              label: 'Log',
              icon: Icons.bug_report,
              color: theme.accentColor,
              enabled: canAct && !isSubmitting,
              state: actionState,
              type: ActionButtonType.log,
              themeAdapter: theme,
              onTap: canAct && !isSubmitting
                  ? () {
                      onAppendLog?.call(
                        OtoExecutionLogDraft(
                          executionID: exec.executionID,
                          runnerID: exec.runnerID,
                          line: '[manual] Log entry appended from UI',
                        ),
                      );
                    }
                  : null,
            ),
            _ActionButton(
              label: 'Artifact',
              icon: Icons.insert_drive_file,
              color: theme.primaryColor,
              enabled: canAct && !isSubmitting,
              state: actionState,
              type: ActionButtonType.artifact,
              themeAdapter: theme,
              onTap: canAct && !isSubmitting
                  ? () {
                      onAppendArtifact?.call(
                        OtoExecutionArtifactDraft(
                          executionID: exec.executionID,
                          runnerID: exec.runnerID,
                          name: 'manual_artifact.txt',
                          path: '/tmp/manual_artifact.txt',
                        ),
                      );
                    }
                  : null,
            ),
          ],
        ),
        if (actionState != null &&
            (actionState.isSuccess || actionState.isFailed)) ...[
          const SizedBox(height: 8),
          Container(
            padding: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: actionState.isFailed
                  ? Colors.red.withValues(alpha: 0.1)
                  : theme.primaryColor.withValues(alpha: 0.1),
              borderRadius: BorderRadius.circular(4),
              border: Border.all(
                color: actionState.isFailed
                    ? Colors.red.withValues(alpha: 0.5)
                    : theme.primaryColor.withValues(alpha: 0.5),
              ),
            ),
            child: Text(
              actionState.message ??
                  (actionState.isSuccess ? 'Succeeded' : 'Failed'),
              style: TextStyle(
                color: actionState.isFailed ? Colors.red : theme.primaryColor,
                fontSize: 12,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
        ],
      ],
    );
  }

  void _showCancelConfirmation(
    BuildContext context,
    OtoExecutionViewModel exec,
  ) {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Cancel Execution'),
          content: Text(
            'Are you sure you want to cancel execution "${exec.executionID}"?',
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('No'),
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.of(context).pop();
                onCancelExecution?.call(
                  OtoExecutionCancelDraft(
                    executionID: exec.executionID,
                    runnerID: exec.runnerID,
                    reason: 'User initiated from UI',
                  ),
                );
              },
              style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
              child: const Text('Cancel'),
            ),
          ],
        );
      },
    );
  }

  Widget _buildLogsContent(OtoConsoleThemeAdapter theme) {
    if (logsSnapshot == null) {
      return Text(
        'No logs loaded.',
        style: TextStyle(
          color: theme.textSecondaryColor,
          fontFamily: 'monospace',
        ),
      );
    }
    switch (logsSnapshot!.state) {
      case OtoSurfaceLoadState.loading:
        return const Center(child: CircularProgressIndicator());
      case OtoSurfaceLoadState.empty:
        return Text(
          'Log is empty.',
          style: TextStyle(
            color: theme.textSecondaryColor,
            fontFamily: 'monospace',
          ),
        );
      case OtoSurfaceLoadState.error:
        return Text(
          'Error loading logs: ${logsSnapshot!.errorMessage}',
          style: const TextStyle(color: Colors.red, fontFamily: 'monospace'),
        );
      case OtoSurfaceLoadState.data:
        final logs = logsSnapshot!.data ?? [];
        if (logs.isEmpty) {
          return Text(
            'Log is empty.',
            style: TextStyle(
              color: theme.textSecondaryColor,
              fontFamily: 'monospace',
            ),
          );
        }
        return SingleChildScrollView(
          child: Text(
            logs.map((e) => '[${e.timestamp}] ${e.line}').join('\n'),
            style: TextStyle(
              color: theme.textColor,
              fontFamily: 'monospace',
              fontSize: 12,
            ),
          ),
        );
    }
  }

  Widget _buildArtifactsContent(OtoConsoleThemeAdapter theme) {
    if (artifactsSnapshot == null) {
      return Text(
        'No artifacts loaded.',
        style: TextStyle(color: theme.textSecondaryColor, fontSize: 13),
      );
    }
    switch (artifactsSnapshot!.state) {
      case OtoSurfaceLoadState.loading:
        return const Center(child: CircularProgressIndicator());
      case OtoSurfaceLoadState.empty:
        return Text(
          'No artifacts produced.',
          style: TextStyle(color: theme.textSecondaryColor, fontSize: 13),
        );
      case OtoSurfaceLoadState.error:
        return Text(
          'Error loading artifacts: ${artifactsSnapshot!.errorMessage}',
          style: const TextStyle(color: Colors.red, fontSize: 13),
        );
      case OtoSurfaceLoadState.data:
        final artifacts = artifactsSnapshot!.data ?? [];
        if (artifacts.isEmpty) {
          return Text(
            'No artifacts produced.',
            style: TextStyle(color: theme.textSecondaryColor, fontSize: 13),
          );
        }
        return Column(
          children: artifacts
              .map(
                (artifact) => Padding(
                  padding: const EdgeInsets.only(bottom: 6),
                  child: ArtifactRecordRow(
                    artifact: artifact,
                    theme: theme,
                    density: ArtifactRowDensity.compact,
                  ),
                ),
              )
              .toList(),
        );
    }
  }
}

enum ActionButtonType { cancel, report, log, artifact }

class _ActionButton extends StatelessWidget {
  final String label;
  final IconData icon;
  final Color color;
  final bool enabled;
  final OtoActionViewState? state;
  final ActionButtonType type;
  final OtoConsoleThemeAdapter themeAdapter;
  final VoidCallback? onTap;

  const _ActionButton({
    required this.label,
    required this.icon,
    required this.color,
    required this.enabled,
    required this.state,
    required this.type,
    required this.themeAdapter,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    Color successColor = themeAdapter.primaryColor;
    Color failColor = Colors.red;
    if (!enabled) {
      successColor = successColor.withValues(alpha: 0.5);
      failColor = failColor.withValues(alpha: 0.5);
    }
    return SizedBox(
      width: 80,
      height: 36,
      child: ElevatedButton(
        onPressed: onTap,
        style: ElevatedButton.styleFrom(
          backgroundColor: enabled ? color : color.withValues(alpha: 0.3),
          foregroundColor: enabled ? Colors.white : Colors.white60,
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          visualDensity: VisualDensity.compact,
        ),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Icon(
              icon,
              size: 14,
              color: enabled ? Colors.white : Colors.white60,
            ),
            Flexible(
              child: Text(
                label,
                style: const TextStyle(
                  fontSize: 11,
                  fontWeight: FontWeight.w600,
                ),
                overflow: TextOverflow.ellipsis,
              ),
            ),
            if (state != null && state!.isSubmitting) ...[
              const SizedBox(width: 4),
              const SizedBox(
                width: 8,
                height: 8,
                child: CircularProgressIndicator(strokeWidth: 1.5),
              ),
            ] else if (state != null && state!.isSuccess) ...[
              const SizedBox(width: 2),
              Icon(Icons.check, size: 12, color: successColor),
            ] else if (state != null && state!.isFailed) ...[
              const SizedBox(width: 2),
              Icon(Icons.close, size: 12, color: failColor),
            ],
          ],
        ),
      ),
    );
  }
}

class _StatusBadge extends StatelessWidget {
  final String state;
  final OtoConsoleThemeAdapter theme;

  const _StatusBadge({required this.state, required this.theme});

  @override
  Widget build(BuildContext context) {
    Color color;
    switch (state.toLowerCase()) {
      case 'queued':
        color = theme.textSecondaryColor;
        break;
      case 'running':
        color = theme.accentColor;
        break;
      case 'succeeded':
        color = theme.primaryColor;
        break;
      case 'failed':
        color = Colors.red;
        break;
      case 'canceled':
        color = Colors.orange;
        break;
      default:
        color = theme.textSecondaryColor;
        break;
    }

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.12),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: color.withValues(alpha: 0.5)),
      ),
      child: Text(
        state.toUpperCase(),
        style: TextStyle(
          color: color,
          fontSize: 11,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

/// Groups execution actions by intent (destructive, reporting, append) so the
/// selected-run detail toolbar mirrors the Jenkins build-page action grouping
/// while keeping the underlying button callbacks unchanged.
class _ExecutionActionToolbar extends StatelessWidget {
  final List<Widget> destructive;
  final List<Widget> reporting;
  final List<Widget> append;

  const _ExecutionActionToolbar({
    required this.destructive,
    required this.reporting,
    required this.append,
  });

  @override
  Widget build(BuildContext context) {
    Widget group(List<Widget> items) =>
        Wrap(spacing: 8, runSpacing: 8, children: items);
    final groups = [
      destructive,
      reporting,
      append,
    ].where((items) => items.isNotEmpty).map(group).toList();
    return Wrap(spacing: 20, runSpacing: 8, children: groups);
  }
}

/// A bounded detail pane inside the selected execution detail with a title and
/// optional trailing action slot, leaving room for future console/artifact
/// actions without changing the pane anatomy.
class _DetailPane extends StatelessWidget {
  final String title;
  final OtoConsoleThemeAdapter theme;
  final Widget child;
  final Widget? action;

  const _DetailPane({
    required this.title,
    required this.theme,
    required this.child,
    this.action,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Expanded(
              child: Text(
                title,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  color: theme.textColor,
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            ?action,
          ],
        ),
        const SizedBox(height: 8),
        child,
      ],
    );
  }
}

/// Bounded monospace console box used by the logs preview pane.
class _LogPreviewBox extends StatelessWidget {
  final OtoConsoleThemeAdapter theme;
  final Widget child;

  const _LogPreviewBox({required this.theme, required this.child});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: BorderRadius.circular(6),
        border: Border.all(color: theme.borderColor),
      ),
      constraints: const BoxConstraints(maxHeight: 180),
      child: child,
    );
  }
}

/// Jenkins build-page-style left task menu for the selected run. Presentational
/// only: OTO has no per-item view (console/artifacts/history all render
/// together below), so entries are static labels rather than tab switches.
class _RunTaskMenu extends StatelessWidget {
  final OtoConsoleThemeAdapter theme;

  const _RunTaskMenu({required this.theme});

  static const _items = [
    (Icons.terminal, 'Console Output'),
    (Icons.account_tree_outlined, 'Pipeline Overview'),
    (Icons.inventory_2_outlined, 'Artifacts'),
    (Icons.history, 'Build History'),
  ];

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 168,
      padding: const EdgeInsets.symmetric(vertical: 8),
      decoration: BoxDecoration(
        color: theme.backgroundColor,
        borderRadius: BorderRadius.circular(6),
        border: Border.all(color: theme.borderColor),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (final (icon, label) in _items)
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
              child: Row(
                children: [
                  Icon(icon, color: theme.textSecondaryColor, size: 15),
                  const SizedBox(width: 8),
                  Expanded(
                    child: Text(
                      label,
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                        color: theme.textSecondaryColor,
                        fontSize: 12,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ],
              ),
            ),
        ],
      ),
    );
  }
}

enum _StageStatus { succeeded, running, failed, canceled, skipped, pending }

typedef _StageNode = (String name, _StageStatus status);

/// Deterministic Pipeline Graph View placeholder. OTO Core does not report
/// per-stage telemetry yet, so a fixed `Checkout/Build/Package/Publish`
/// skeleton is colored from the run's overall state instead of leaving the
/// region empty.
class _PipelineStageGraph extends StatelessWidget {
  final OtoConsoleThemeAdapter theme;
  final OtoExecutionViewModel exec;

  const _PipelineStageGraph({required this.theme, required this.exec});

  static const _stageNames = ['Checkout', 'Build', 'Package', 'Publish'];

  List<_StageNode> get _stages {
    switch (exec.state.toLowerCase()) {
      case 'succeeded':
        return [for (final name in _stageNames) (name, _StageStatus.succeeded)];
      case 'failed':
        return [
          (_stageNames[0], _StageStatus.succeeded),
          (_stageNames[1], _StageStatus.failed),
          (_stageNames[2], _StageStatus.skipped),
          (_stageNames[3], _StageStatus.skipped),
        ];
      case 'running':
        return [
          (_stageNames[0], _StageStatus.succeeded),
          (_stageNames[1], _StageStatus.running),
          (_stageNames[2], _StageStatus.pending),
          (_stageNames[3], _StageStatus.pending),
        ];
      case 'canceled':
        return [
          (_stageNames[0], _StageStatus.succeeded),
          (_stageNames[1], _StageStatus.canceled),
          (_stageNames[2], _StageStatus.skipped),
          (_stageNames[3], _StageStatus.skipped),
        ];
      case 'queued':
      default:
        return [for (final name in _stageNames) (name, _StageStatus.pending)];
    }
  }

  Color _colorFor(_StageStatus status) {
    switch (status) {
      case _StageStatus.succeeded:
        return theme.primaryColor;
      case _StageStatus.running:
        return theme.accentColor;
      case _StageStatus.failed:
        return Colors.red;
      case _StageStatus.canceled:
        return Colors.orange;
      case _StageStatus.skipped:
      case _StageStatus.pending:
        return theme.textSecondaryColor;
    }
  }

  @override
  Widget build(BuildContext context) {
    final stages = _stages;
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Stage Graph',
          style: TextStyle(
            color: theme.textColor,
            fontSize: 13,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        Wrap(
          spacing: 12,
          runSpacing: 8,
          children: [
            for (final stage in stages) _StageChip(stage: stage, color: _colorFor(stage.$2)),
          ],
        ),
        const SizedBox(height: 8),
        Text(
          'Per-stage telemetry is not reported yet; the graph reflects the overall run state.',
          style: TextStyle(color: theme.textSecondaryColor, fontSize: 11),
        ),
      ],
    );
  }
}

class _StageChip extends StatelessWidget {
  final _StageNode stage;
  final Color color;

  const _StageChip({required this.stage, required this.color});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.12),
        borderRadius: BorderRadius.circular(6),
        border: Border.all(color: color.withValues(alpha: 0.5)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(Icons.circle, color: color, size: 8),
          const SizedBox(width: 6),
          Text(
            stage.$1,
            style: TextStyle(color: color, fontSize: 12, fontWeight: FontWeight.w700),
          ),
        ],
      ),
    );
  }
}

/// Shown in the detail region when no run is selected in the list.
class _RunDetailPlaceholder extends StatelessWidget {
  final OtoConsoleThemeAdapter theme;

  const _RunDetailPlaceholder({required this.theme});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: theme.railBackgroundColor,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: theme.borderColor),
      ),
      child: Row(
        children: [
          Icon(
            Icons.play_circle_outline,
            color: theme.textSecondaryColor,
            size: 20,
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Text(
              'Select a run to view details.',
              style: TextStyle(color: theme.textSecondaryColor, fontSize: 13),
            ),
          ),
        ],
      ),
    );
  }
}
