/**
 * defineTrait() — Factory for creating composable behavior traits.
 *
 * A trait is a reusable behavior package: event listeners, attribute management,
 * and cleanup — defined once, attached to any element. The trait file is the
 * single source of truth: name, category, description, attributes, events,
 * and config all live here, and the public catalog (a2ui-corpus retrieval/
 * catalog.js) is generated from the live registry — never hand-curated.
 *
 *   import { defineTrait } from './define.js';
 *
 *   export const pressable = defineTrait({
 *     name: 'pressable',
 *     category: 'input-interaction',
 *     description: 'Normalizes click/tap/keyboard into a single "press" event',
 *     attributes: ['data-pressable-pressed'],
 *     events: ['press'],
 *     config: [],
 *     setup({ host }) {
 *       // attach behavior, return cleanup function
 *     },
 *   });
 *
 * Usage:
 *   // On any DOM element:
 *   const inst = pressable();
 *   inst.connect(myElement);
 *   inst.disconnect(myElement);
 *
 *   // On a UIElement:
 *   class MyButton extends UIElement {
 *     static traits = [pressable];
 *   }
 */

const registry = new Map();

const KNOWN_CATEGORIES = new Set([
  'input-interaction',
  'keyboard-navigation',
  'forms-data',
  'layout-measurement',
  'motion-positioning',
  'animation-feedback',
  'visual-dynamics',
  'interaction-delight',
  'audio-haptics-sensory',
]);

export function defineTrait(spec) {
  if (!spec.name) throw new Error('Trait requires a name');
  if (!spec.setup) throw new Error(`Trait "${spec.name}" requires a setup function`);
  if (!spec.category) throw new Error(`Trait "${spec.name}" requires a category`);
  if (!KNOWN_CATEGORIES.has(spec.category)) {
    throw new Error(`Trait "${spec.name}" has unknown category "${spec.category}". Known: ${[...KNOWN_CATEGORIES].join(', ')}`);
  }
  if (!spec.description) throw new Error(`Trait "${spec.name}" requires a description`);

  const schema = Object.freeze({
    name: spec.name,
    category: spec.category,
    description: spec.description,
    attributes: Object.freeze(spec.attributes || []),
    events: Object.freeze(spec.events || []),
    config: Object.freeze(spec.config || []),
  });

  function factory() {
    let cleanup = null;

    return {
      schema,
      connect(host, ctx) {
        cleanup = spec.setup({ host, ...(ctx || {}) });
      },
      disconnect(host) {
        if (cleanup) { cleanup(); cleanup = null; }
        // Validate: warn if managed attributes are still present
        for (const attr of schema.attributes) {
          if (host.hasAttribute(attr)) {
            console.warn(`Trait "${schema.name}": attribute "${attr}" still present after disconnect`);
            host.removeAttribute(attr);
          }
        }
      },
    };
  }

  factory.schema = schema;
  registry.set(spec.name, factory);
  return factory;
}

/**
 * Look up a trait factory by its declared name.
 * Returns null if no trait with that name has been imported yet.
 *
 * Used by UIElement to resolve declarative `<comp-ui traits="pressable …">`
 * attributes — the named trait must have been imported somewhere in the
 * app before the element is connected, otherwise the lookup misses.
 */
export function getTrait(name) {
  return registry.get(name) || null;
}

/**
 * Look up a trait's frozen schema by name. Convenience wrapper over
 * `getTrait(name)?.schema`.
 */
export function getTraitSchema(name) {
  return registry.get(name)?.schema || null;
}

export function listTraits() {
  return [...registry.keys()];
}
