// Studio theme-plugin helpers: discovery, loading, persistence.
//
// `studio.theme.*` capabilities returned by the hub's
// `list_capabilities` are eligible theme plugins. Each one
// exposes a `theme` hook that returns a ColorScheme for a
// given brightness. This module wraps that into the shape
// Flutter's MaterialApp expects: a pair of `ThemeData`
// (light + dark) built from the plugin's tokens, ready to
// drop into the running app.

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

import '../theme/theme.dart';
import 'hub.dart';

/// SharedPreferences key for the operator's preferred theme
/// plugin. `null` (or absent) means "use Studio's built-in
/// default" (ChainTheme.light / .dark).
const _kThemePluginKey = 'theme_plugin_capability';

/// One ColorScheme pair as built from a theme-plugin's
/// `theme_for("light")` + `theme_for("dark")` round-trips.
/// `null` slots are intentional — when the plugin declines
/// one brightness the caller falls back to its built-in
/// scheme for that side and uses the plugin's only for the
/// other.
class ThemePluginSchemes {
  final ColorScheme? light;
  final ColorScheme? dark;
  const ThemePluginSchemes({this.light, this.dark});

  bool get isEmpty => light == null && dark == null;
}

/// List every installed `studio.theme.*` capability the hub
/// is currently able to dispatch. Returns an empty list when
/// no theme plugin is installed (typical fresh hub).
Future<List<String>> listThemePluginCapabilities() async {
  final caps = await HubService.instance.allCapabilities();
  return caps
      .where(
        (c) => c.kind == 'wasm' && c.capability.startsWith('studio.theme.'),
      )
      .map((c) => c.capability)
      .toList()
    ..sort();
}

/// Read the operator's preferred theme-plugin capability from
/// SharedPreferences. Returns `null` when none chosen.
Future<String?> loadActiveThemePlugin() async {
  final prefs = await SharedPreferences.getInstance();
  final v = prefs.getString(_kThemePluginKey);
  if (v == null || v.isEmpty) return null;
  return v;
}

/// Persist the operator's theme-plugin choice. Pass `null` to
/// clear it (back to the built-in default).
Future<void> saveActiveThemePlugin(String? capability) async {
  final prefs = await SharedPreferences.getInstance();
  if (capability == null || capability.isEmpty) {
    await prefs.remove(_kThemePluginKey);
  } else {
    await prefs.setString(_kThemePluginKey, capability);
  }
}

/// Invoke the plugin twice (light + dark) and translate its
/// 14 ARGB tokens into a `ColorScheme` per brightness. Plugin
/// errors are swallowed per brightness — a plugin that
/// declines `dark` but accepts `light` still contributes the
/// light scheme; the caller falls back to the built-in dark.
Future<ThemePluginSchemes> loadThemePluginSchemes(String capability) async {
  ColorScheme? light;
  ColorScheme? dark;
  try {
    final r = await HubService.instance.invokePluginTheme(
      capability: capability,
      brightness: 'light',
    );
    light = _tokensToColorScheme(r.tokens, Brightness.light);
  } catch (_) {
    // Declined / misconfigured / unreachable. Fall through —
    // built-in light scheme will fill the slot.
  }
  try {
    final r = await HubService.instance.invokePluginTheme(
      capability: capability,
      brightness: 'dark',
    );
    dark = _tokensToColorScheme(r.tokens, Brightness.dark);
  } catch (_) {}
  return ThemePluginSchemes(light: light, dark: dark);
}

/// Build a `ThemeData` from a plugin-supplied `ColorScheme`.
/// Delegates to [ChainTheme.fromColorScheme] so plugin-themed
/// builds and built-in themes share one structural shape — same
/// textTheme cascade, same component themes, same `inherit`
/// values on every TextStyle. Without that, Flutter's
/// AnimatedDefaultTextStyle lerp blows up when swapping between
/// the two paths.
ThemeData themeDataFromScheme(ColorScheme scheme) =>
    ChainTheme.fromColorScheme(scheme);

/// Translate the plugin's 14 ARGB tokens into a `ColorScheme`.
/// Order matches the plugin contract:
///   primary, on-primary, secondary, on-secondary,
///   tertiary, on-tertiary, error, on-error,
///   surface, on-surface, surface-variant, on-surface-variant,
///   outline, outline-variant.
/// Length mismatches are tolerated — missing slots fall back
/// to a sensible Material 3 baseline so a half-built plugin
/// doesn't crash the picker.
ColorScheme _tokensToColorScheme(List<int> tokens, Brightness brightness) {
  Color slot(int i) {
    if (i < tokens.length) return Color(tokens[i]);
    // Reasonable fallback when the plugin underdelivered.
    return brightness == Brightness.dark
        ? const Color(0xFF1A1A1A)
        : const Color(0xFFEEEEEE);
  }

  return ColorScheme(
    brightness: brightness,
    primary: slot(0),
    onPrimary: slot(1),
    secondary: slot(2),
    onSecondary: slot(3),
    tertiary: slot(4),
    onTertiary: slot(5),
    error: slot(6),
    onError: slot(7),
    surface: slot(8),
    onSurface: slot(9),
    surfaceContainer: slot(10),
    onSurfaceVariant: slot(11),
    outline: slot(12),
    outlineVariant: slot(13),
  );
}
