// src/framework/components/MxTheme.ts
//
// MxTheme — Network-controllable global theme actor.
//
// Sets --mx-* CSS custom properties on document.documentElement (:root).
// All components using var(--mx-*, fallback) inherit these values
// through the CSS cascade, including through shadow DOM boundaries.
//
// Supports preset themes (light/dark) and individual token overrides.
// AI-addressable: send applyPreset or setTokens to this actor's $inbox.

import { MatrixActorHtmlElement } from '../MatrixActorHtmlElement';
import { RequestReply } from '../../engine/remoting/RequestReply';
import {
  type MxThemeTokens,
  MX_THEME_PRESETS,
  MX_THEME_LIGHT,
} from '../theme/tokens';

export class MxTheme extends MatrixActorHtmlElement {
  static description = 'Theme controller — applies color presets and design tokens to the entire component tree. Invisible actor (no DOM).';

  static accepts = {
    applyPreset: { preset: 'string' },
    setTokens:   { tokens: 'any' },
    getTheme:    {},
  };

  static emits = {
    themeChanged: { preset: 'string?', tokens: 'any' },
  };

  static moduleUrl = import.meta.url;

  // No visible DOM — invisible actor
  static template = '';

  private _currentPreset: string = 'light';
  private _currentTokens: MxThemeTokens = { ...MX_THEME_LIGHT };

  // User-scoped: theme preferences survive new sessions, not just F5 reload.
  // KV key: user.default.chat.theme (cascade: user → root → global)
  static stateScope = 'user' as const;

  /**
   * Bootstrap: apply initial tokens immediately (DOM attr or default),
   * then async-read persisted preference from daemon's preferences service.
   * If a persisted preset exists, it overwrites the initial default.
   */
  async onBootstrap(): Promise<void> {
    await super.onBootstrap();
    // 1. Immediate: apply DOM attribute or light default for instant visual feedback
    const attr = this.getAttribute('data-preset');
    if (attr && MX_THEME_PRESETS[attr]) {
      this._currentPreset = attr;
      this._currentTokens = { ...MX_THEME_PRESETS[attr] };
    }
    this._applyToRoot(this._currentTokens);

    // 2. Async: read persisted user preference from daemon preferences service
    this._restoreFromPreferences();
  }

  /**
   * Read the user's persisted theme preference via the daemon preferences service.
   * Fire-and-forget — never blocks bootstrap or breaks the UI.
   */
  protected async _restoreFromPreferences(): Promise<void> {
    try {
      const ctx = this._context;
      if (!ctx) return;

      const daemonRoot = this._resolveDaemonRoot();

      const prefsMount = `${daemonRoot}/system.preferences`;
      const result = await RequestReply.execute(ctx, prefsMount, 'preferences.getEffective', {
        mount: 'chat',
        key: 'theme.preset',
      }, { timeoutMs: 3000 }) as { value?: unknown; source?: string } | null;

      if (result?.value && typeof result.value === 'string' && MX_THEME_PRESETS[result.value]) {
        if (result.value !== this._currentPreset) {
          this._currentPreset = result.value;
          this._currentTokens = { ...MX_THEME_PRESETS[result.value] };
          this._applyToRoot(this._currentTokens);
        }
      }
    } catch {
      // Preferences service unavailable — keep current preset. Not an error.
    }
  }

  /**
   * Apply a named preset theme (e.g., 'light', 'dark').
   * Sets all tokens on :root, persists to scoped state, and writes to preferences service.
   */
  onApplyPreset(p: { preset: string }): { ok: boolean; preset?: string; tokens?: MxThemeTokens; error?: string } {
    const preset = MX_THEME_PRESETS[p.preset];
    if (!preset) {
      this.warnKernel(`[MxTheme] Unknown preset: "${p.preset}". Available: ${Object.keys(MX_THEME_PRESETS).join(', ')}`);
      return { ok: false, error: `Unknown preset: ${p.preset}` };
    }
    this._currentPreset = p.preset;
    this._currentTokens = { ...preset };
    this._applyToRoot(this._currentTokens);
    this.setState({ preset: p.preset, tokens: { ...preset } });

    // Persist to preferences service (fire-and-forget)
    this._persistToPreferences(p.preset);
    return { ok: true, preset: this._currentPreset, tokens: { ...this._currentTokens } };
  }

  /**
   * Write the user's theme choice to the daemon preferences service.
   * Fire-and-forget — never blocks the UI.
   */
  protected async _persistToPreferences(preset: string): Promise<void> {
    try {
      const ctx = this._context;
      if (!ctx) return;

      const daemonRoot = this._resolveDaemonRoot();

      const prefsMount = `${daemonRoot}/system.preferences`;
      await RequestReply.execute(ctx, prefsMount, 'preferences.set', {
        mount: 'chat',
        key: 'theme.preset',
        value: preset,
        scope: 'user',
      }, { timeoutMs: 3000 });
    } catch {
      // Preferences service unavailable — state still persists via NatsStateTap.
    }
  }

  /**
   * Set individual tokens (partial override).
   * Merges over current tokens and applies to :root.
   */
  onSetTokens(p: { tokens: Record<string, string> }): { ok: boolean; preset?: string; tokens?: MxThemeTokens; error?: string } {
    if (!p?.tokens || typeof p.tokens !== 'object' || Array.isArray(p.tokens) || Object.keys(p.tokens).length === 0) {
      return { ok: false, error: 'tokens payload is required' };
    }
    const merged = { ...this._currentTokens, ...p.tokens } as MxThemeTokens;
    this._currentPreset = 'custom';
    this._currentTokens = merged;
    this._applyToRoot(this._currentTokens);
    this.setState({ preset: 'custom', tokens: merged });
    return { ok: true, preset: this._currentPreset, tokens: { ...this._currentTokens } };
  }

  /**
   * Return current theme state (for AI inspection).
   */
  onGetTheme(): { preset: string; tokens: MxThemeTokens } {
    return { preset: this._currentPreset, tokens: { ...this._currentTokens } };
  }

  /**
   * Apply tokens to document.documentElement (:root).
   * CSS custom properties set here cascade into ALL shadow DOMs.
   */
  private _applyToRoot(tokens: Partial<MxThemeTokens>): void {
    const root = document.documentElement;
    for (const [key, value] of Object.entries(tokens)) {
      if (value !== undefined && value !== null) {
        root.style.setProperty(`--mx-${key}`, value as string);
      }
    }
  }
}

// Alias for naming consistency
export { MxTheme as MxThemeShell };
