// Single entry point for surfacing an error to the operator.
// Every error pathway Studio shows (SnackBar, dialog, inline
// strip) should go through one of these helpers so the
// operator gets:
//
//   • a copyable, selectable rendition of the message
//   • a friendly headline (gRPC codes mapped via
//     friendlyError) instead of a wall-of-stack-trace
//   • an automatic append to ~/.chain/logs/studio-errors.log so
//     the trail survives the dismiss action
//
// Call sites used to scatter `ScaffoldMessenger.of(ctx).showSnackBar(
// SnackBar(content: Text(e.toString())))` — that violated the
// "errors must be copyable" rule everywhere. These helpers
// replace those open-coded sites.

import 'package:flutter/material.dart';

import '../widgets/chain_error_box.dart';
import '../theme/tokens.dart';
import 'chain_log.dart';

/// Show [error] as a SnackBar with a copy affordance.
///
/// [source] is a short tag (`flows.run`, `audit.clear`, …)
/// used to group entries in the on-disk log so an operator or
/// `chain doctor` can scan recent failures by area without
/// reading the full free-text. Keep it stable; treat it like
/// a metric label, not free-form text.
///
/// [context] is the build-context the snack lands on. Falls
/// back to a Material default duration of 6 seconds — twice
/// the framework default so the operator has time to click
/// copy on an unfamiliar message.
void showFaiErrorSnack(
  BuildContext context,
  String source,
  Object error, {
  String? title,
  Duration duration = const Duration(seconds: 6),
}) {
  // Fire-and-forget log write. If the disk's full or the dir
  // is read-only the UI still surfaces the error.
  ChainLog.instance.error(source, error, context: title);
  final messenger = ScaffoldMessenger.maybeOf(context);
  if (messenger == null) {
    // No Scaffold above — the caller is likely showing the
    // error from inside a dialog. We've already logged; bail
    // rather than throw.
    return;
  }
  messenger.hideCurrentSnackBar();
  final theme = Theme.of(context);
  messenger.showSnackBar(
    SnackBar(
      duration: duration,
      behavior: SnackBarBehavior.floating,
      // Default SnackBar padding crushes our error box; reset
      // to zero and let the box's own padding take over.
      padding: EdgeInsets.zero,
      content: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: ChainSpace.sm,
          vertical: ChainSpace.xs,
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            if (title != null) ...[
              Text(
                title,
                style: theme.textTheme.titleSmall?.copyWith(
                  color: theme.colorScheme.error,
                  fontWeight: FontWeight.w600,
                ),
              ),
              const SizedBox(height: ChainSpace.xs),
            ],
            ChainErrorBox(error: error, isError: true),
          ],
        ),
      ),
    ),
  );
}

/// Surface a failed external-process result (daemon start, binary
/// probe, file-open) as a copyable error snack. The `ok/stdout/stderr`
/// record these actions return is not a thrown error, so call sites used
/// to drop it into a raw `SnackBar(Text(...))` — uncopyable, the exact
/// thing the operator needs to paste back. This joins stderr + stdout
/// into one selectable, one-tap-copyable, logged message.
void showFaiProcessError(
  BuildContext context,
  String source,
  String stdout,
  String stderr, {
  String? title,
}) {
  final detail = [stderr.trim(), stdout.trim()]
      .where((s) => s.isNotEmpty)
      .join('\n\n');
  showFaiErrorSnack(
    context,
    source,
    detail.isEmpty ? 'process exited non-zero (no output)' : detail,
    title: title,
  );
}

/// Like [showFaiProcessError] but as a persistent modal dialog. Use for
/// failures the operator needs time to read and copy (a daemon that
/// won't start) — a SnackBar flashes away before they can act on it.
Future<void> showFaiProcessErrorDialog(
  BuildContext context,
  String source,
  String stdout,
  String stderr, {
  String? title,
}) {
  final detail = [stderr.trim(), stdout.trim()]
      .where((s) => s.isNotEmpty)
      .join('\n\n');
  return showFaiErrorDialog(
    context,
    source,
    detail.isEmpty ? 'process exited non-zero (no output)' : detail,
    title: title,
  );
}

/// Show [error] as a centered modal dialog. Use this when the
/// failure blocks meaningful interaction (auth missing, hub
/// unreachable) — a SnackBar would be too easy to dismiss.
Future<void> showFaiErrorDialog(
  BuildContext context,
  String source,
  Object error, {
  String? title,
}) async {
  ChainLog.instance.error(source, error);
  return showDialog<void>(
    context: context,
    builder: (ctx) => AlertDialog(
      title: title == null ? null : Text(title),
      content: SizedBox(
        width: 480,
        child: ChainErrorBox(error: error, isError: true, maxHeight: 240),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(ctx).maybePop(),
          child: const Text('OK'),
        ),
      ],
    ),
  );
}
