/**
 * Non-side-effect class export for `<theme-provider>`.
 *
 * Importing this file gives you the class (and adopts the foundation as a
 * module-eval side effect) without auto-registering the tag. The auto-register
 * path is `@adia-ai/web-components/components/theme-provider` (which imports this
 * file + calls `defineIfFree()`).
 *
 * `<theme-provider>` — the foundation-providing wrapper. Adopt the AdiaUI
 * foundation (design tokens + resets + page-frame + every primitive's CSS) into
 * the document from anywhere in the DOM, so a surface renders fully-styled
 * WITHOUT a hand-wired `<link rel="stylesheet">` in `<head>`. Layout-transparent
 * (`display: contents`): the element owns no box; children lay out as if it
 * weren't there.
 *
 * Mechanism: imports the constructable-stylesheet twin of `web-components.min.css`
 * — byte-identical to the CDN bundle (emitted from the same buffer by
 * `scripts/build/bundle-css.mjs`) — and adopts it once into
 * `document.adoptedStyleSheets`, deduped by sheet identity. Adoption fires at
 * module evaluation (the earliest point), so importing the element provides the
 * foundation before the body paints in the common case.
 *
 * Coexists with the render-blocking `<link>` path — both deliver the same bytes.
 * Use a `<link>` (→ the CDN `web-components.min.css`) for multi-page / top-level
 * surfaces (cacheable across navigations, zero flash); use `<theme-provider>`
 * for SPA roots, embedded apps, and dynamic mounts where you don't control
 * `<head>` (e.g. `<embed-shell>`, an A2UI surface, a micro-frontend).
 *
 * Scope (v1): the provider supplies the FOUNDATION only — design tokens, resets,
 * page-frame, and every primitive's CSS. OS light/dark resolves automatically
 * (the tokens are `light-dark()`-based). Named themes (`[theme]`) and the
 * `verse` / `prose` context registers live in OPT-IN layers (themes.css /
 * verse.css / prose.css) that are NOT in the foundation bundle, so setting
 * `theme` / `verse` on the wrapper does nothing until those layers are also
 * adopted — a planned `theme=` / `verse` provider option (the token-root selector
 * is pre-wired for it; see .brain/notes/theme-provider-design-2026-06-04.md).
 *
 * Deliberately NOT in the all-in-one `@adia-ai/web-components` barrel — it's
 * opt-in infra (it carries the whole foundation). Import it explicitly:
 *   `import '@adia-ai/web-components/components/theme-provider';`
 *
 * @see ../../USAGE.md  ·  .brain/notes/theme-provider-design-2026-06-04.md
 */

import { UIElement } from '../../core/element.js';
import constructFoundationSheet from '../../dist/web-components.sheet.js';

/**
 * Adopt the foundation sheet into the document exactly once. Idempotent across
 * any number of `<theme-provider>` instances (dedup by sheet identity). No-ops in
 * a non-DOM context — `constructFoundationSheet()` returns `null` on SSR/Node.
 */
function provideFoundation() {
  const sheet = constructFoundationSheet();
  if (!sheet) return;
  if (!document.adoptedStyleSheets.includes(sheet)) {
    document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
  }
}

// Opt-in context registers — themes (named `[theme]` presets), and the `verse` /
// `prose` `scale` registers. Each is its own constructable-sheet twin (built by
// scripts/build/bundle-css.mjs, byte-identical to its .min.css). Adopted on demand
// the first time a provider needs one, deduped by sheet identity. Dynamic-imported
// (static paths, so bundlers can code-split) so the element stays lean — a provider
// that does no theming never pulls these in.
const TWIN_LOADED = { themes: false, verse: false, prose: false };
function loadTwin(name) {
  switch (name) {
    case 'themes': return import('../../dist/themes.sheet.js');
    case 'verse': return import('../../dist/verse.sheet.js');
    case 'prose': return import('../../dist/prose.sheet.js');
    default: return null;
  }
}
function ensureTwin(name) {
  if (TWIN_LOADED[name]) return;
  const p = loadTwin(name);
  if (!p) return;
  TWIN_LOADED[name] = true;
  p.then((mod) => {
    const sheet = mod?.default?.();
    if (sheet && !document.adoptedStyleSheets.includes(sheet)) {
      document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
    }
  }).catch(() => { TWIN_LOADED[name] = false; }); // allow a later retry on failure
}

// Adopt the base foundation as early as this module evaluates — before any element
// connects, before the body paints when imported from <head>. Importing the
// provider IS the act of providing the foundation, wherever the element lands.
provideFoundation();

export class UIThemeProvider extends UIElement {
  static properties = {
    // Named theme preset (ocean / forest / slate / …). Reflected, so the attribute
    // — which IS the `[theme="…"]` CSS hook in themes.css — tracks the property.
    theme: { type: String, reflect: true },
    // Typographic/density register: "verse" (compact) | "prose" (long-form).
    // Mapped onto the existing `[verse]` / `[prose]` attribute below (additive —
    // the register CSS is untouched), so every register rule incl. `[size]`
    // sub-tiers applies for free.
    scale: { type: String, reflect: true },
  };
  static template = () => null;

  connected() {
    // Layout-transparent + invisible to the a11y tree (the template-engine /
    // traits-host convention). Set imperatively so it holds even in the frame
    // before the adopted foundation's own `display: contents` rule applies.
    this.style.display = 'contents';
    if (!this.hasAttribute('role')) this.setAttribute('role', 'presentation');
    provideFoundation(); // belt-and-suspenders for late registration
    this.#applyContext();
  }

  updated() {
    this.#applyContext();
  }

  // Adopt the opt-in twins for the current theme/scale, and map `scale` onto the
  // existing register attribute. Idempotent (safe to call on every update).
  #applyContext() {
    // `theme="ocean"` already matches `[theme="ocean"]` (the attr is on the host);
    // adopt themes.sheet.js so those preset rules exist in the document.
    if (this.theme) ensureTwin('themes');

    const scale = this.scale;
    this.toggleAttribute('verse', scale === 'verse');
    this.toggleAttribute('prose', scale === 'prose');
    if (scale === 'verse') ensureTwin('verse');
    else if (scale === 'prose') ensureTwin('prose');
  }
}
