// FlowsPage — Studio's flow surface. Thin host wrapper around
// the swappable fai_studio_flow_editor package: loads the
// hub's installed capabilities, hands them + a Studio-side
// FlowRunDriver to the editor, and lets the editor own
// everything else (file list, three-tab body, properties
// panel, run progress, …).
//
// Pre-0.52 versions split file-listing and run-dialog state
// across this page. v0.52 moves all of that into the editor
// package so a swap is one pubspec change — Studio doesn't
// need to know what's inside the editor any more.

import 'package:fai_studio_flow_editor/fai_studio_flow_editor.dart';
import 'package:flutter/material.dart';

import '../data/flow_run_driver.dart';
import '../data/hub.dart';
import '../l10n/app_localizations.dart';

class FlowsPage extends StatefulWidget {
  /// Pre-load this flow when the editor first builds. Studio
  /// keeps the parameter so the Cmd+K palette + welcome page
  /// can deep-link straight into a specific flow.
  final String? initialFlowName;

  const FlowsPage({super.key, this.initialFlowName});

  @override
  State<FlowsPage> createState() => _FlowsPageState();
}

class _FlowsPageState extends State<FlowsPage> {
  late Future<List<String>> _capabilities;
  late final StudioFlowRunDriver _driver;

  /// Snapshot of bare capability names the public store can
  /// install. Refreshed once at open so the editor's analyzer
  /// can decide between "Install" (in store) and "Add source"
  /// (not in store) without a per-keystroke network call.
  List<String> _storeCaps = const [];

  @override
  void initState() {
    super.initState();
    _driver = StudioFlowRunDriver();
    _capabilities = _loadCapabilities();
    _loadStoreCapabilities();
  }

  Future<void> _loadStoreCapabilities() async {
    try {
      final items = await HubService.instance.searchStore(limit: 500);
      if (!mounted) return;
      // Store items carry a list of provided capabilities. We
      // ingest all of them flat so a single store entry
      // shipping multiple caps still reports each as installable.
      // Falls back to the item name for legacy entries.
      final caps = <String>{};
      for (final item in items) {
        if (item.requiresCapabilities.isNotEmpty) {
          caps.addAll(item.requiresCapabilities);
        }
        caps.add(item.name);
      }
      setState(() => _storeCaps = caps.toList()..sort());
    } catch (_) {
      // Soft-fail: empty list disables the Install button on
      // unknown-cap fixes, but Add-source remains available.
    }
  }

  /// Pulls the capability identifiers the picker dialog
  /// offers when adding a step. `<capability>@<version>` so
  /// the operator can see exactly what they'll be wiring in,
  /// matching the format the `use:` field expects. Soft-fails
  /// to an empty list if the hub is unreachable — the editor
  /// falls back to a free-form text field in that case.
  Future<List<String>> _loadCapabilities() async {
    try {
      final caps = await HubService.instance.allCapabilities();
      final entries =
          caps.map((c) => '${c.capability}@${c.version}').toSet().toList()
            ..sort();
      return entries;
    } catch (_) {
      return const <String>[];
    }
  }

  /// Quick-fix handler — invoked when the operator clicks
  /// `Install <capability>` on an analyzer issue. Parses the
  /// editor-supplied `provider/name@version` spec into the
  /// Hub's install API and returns the refreshed capability
  /// list so the editor can re-analyze without waiting for the
  /// parent widget rebuild.
  Future<List<String>?> _onInstallCapability(String capability) async {
    return _runInstall(source: capability);
  }

  /// `Add source for <cap>…` handler. Prompts the operator
  /// for a local path or URL, then routes through the same
  /// install RPC. Returns the refreshed capability list on
  /// success, null on cancel / failure.
  Future<List<String>?> _onAddModuleSource(String capability) async {
    final source = await _AddModuleSourceDialog.show(context, capability);
    if (source == null || source.isEmpty) return null;
    return _runInstall(source: source);
  }

  Future<List<String>?> _runInstall({required String source}) async {
    try {
      await HubService.instance.installModule(source: source);
      final caps = await HubService.instance.allCapabilities();
      final updated = caps
          .map((c) => '${c.capability}@${c.version}')
          .toSet()
          .toList()
        ..sort();
      if (mounted) {
        setState(() => _capabilities = Future.value(updated));
      }
      return updated;
    } catch (e) {
      if (mounted) {
        final l = AppLocalizations.of(context);
        final msg = l != null
            ? l.installFailed(e.toString())
            : 'Install failed: $e';
        ScaffoldMessenger.of(context).removeCurrentSnackBar();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(msg),
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
      return null;
    }
  }

  @override
  Widget build(BuildContext context) {
    final locale = Localizations.localeOf(context);
    final editorLocale = locale.languageCode == 'de'
        ? FlowEditorLocale.de
        : FlowEditorLocale.en;
    return FutureBuilder<List<String>>(
      future: _capabilities,
      builder: (context, snap) {
        final caps = snap.data ?? const <String>[];
        return FlowEditorPage(
          initialFlowName: widget.initialFlowName,
          locale: editorLocale,
          runDriver: _driver,
          availableCapabilities: caps,
          storeCapabilities: _storeCaps,
          onInstallCapability: _onInstallCapability,
          onAddModuleSource: _onAddModuleSource,
        );
      },
    );
  }
}

/// Modal asking the operator where a missing capability can be
/// installed from — either a local module directory (the
/// `--link` install path) or a remote URL the hub can resolve.
/// Returns the typed source string on submit, null on cancel.
class _AddModuleSourceDialog extends StatefulWidget {
  final String capability;
  const _AddModuleSourceDialog({required this.capability});

  static Future<String?> show(BuildContext context, String capability) {
    return showDialog<String>(
      context: context,
      builder: (_) => _AddModuleSourceDialog(capability: capability),
    );
  }

  @override
  State<_AddModuleSourceDialog> createState() => _AddModuleSourceDialogState();
}

class _AddModuleSourceDialogState extends State<_AddModuleSourceDialog> {
  final TextEditingController _ctrl = TextEditingController();
  String _source = '';

  @override
  void dispose() {
    _ctrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final l = AppLocalizations.of(context)!;
    return AlertDialog(
      title: Text(l.addSourceTitle),
      content: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 520),
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                l.addSourceIntro(widget.capability),
                style: theme.textTheme.bodySmall?.copyWith(
                  color: theme.colorScheme.onSurfaceVariant,
                ),
              ),
              const SizedBox(height: 16),
              TextField(
                controller: _ctrl,
                autofocus: true,
                onChanged: (v) => setState(() => _source = v.trim()),
                decoration: InputDecoration(
                  labelText: l.addSourceField,
                  hintText: l.addSourceHint,
                  border: const OutlineInputBorder(),
                  isDense: true,
                ),
              ),
              const SizedBox(height: 12),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: theme.colorScheme.surfaceContainerHigh,
                  borderRadius: BorderRadius.circular(6),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      l.addSourceHowItWorksTitle,
                      style: theme.textTheme.labelMedium?.copyWith(
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                    const SizedBox(height: 6),
                    Text(
                      l.addSourceHowItWorksBody,
                      style: theme.textTheme.bodySmall?.copyWith(
                        color: theme.colorScheme.onSurfaceVariant,
                      ),
                    ),
                    const SizedBox(height: 6),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: theme.colorScheme.surface,
                        borderRadius: BorderRadius.circular(4),
                      ),
                      child: SelectableText(
                        l.addSourceCliExample,
                        style: TextStyle(
                          fontFamily: 'JetBrains Mono',
                          fontFamilyFallback: const [
                            'Menlo',
                            'Consolas',
                            'Courier New',
                            'monospace',
                          ],
                          fontSize: 11,
                          color: theme.colorScheme.onSurface,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: Text(l.addSourceCancel),
        ),
        FilledButton(
          onPressed: _source.isEmpty
              ? null
              : () => Navigator.of(context).pop(_source),
          child: Text(l.addSourceInstallButton),
        ),
      ],
    );
  }
}
