/**
 * Shared utilities for Web Component wrappers.
 *
 * @generated by scripts/generate-wc-components.ts
 * DO NOT EDIT MANUALLY
 */

import React from 'react';
import { createRoot } from 'react-dom/client';

export type PropType = 'string' | 'number' | 'boolean' | 'json';
export type PropDefs = Record<string, PropType>;

export function parseAttr(value: string | null, type: PropType): unknown {
  if (value === null) return undefined;
  switch (type) {
    case 'number':
      return Number(value);
    case 'boolean':
      return value !== 'false' && value !== '0';
    case 'json':
      try {
        return JSON.parse(value);
      } catch {
        return value;
      }
    default:
      return value;
  }
}

export function parseValue(value: unknown, type: PropType): unknown {
  if (type === 'boolean' && typeof value === 'boolean') return value;
  if (type === 'number' && typeof value === 'number') return value;
  if (type === 'string' && typeof value === 'string') return value;
  if (type === 'json' && typeof value === 'object' && value !== null) return value;

  if (typeof value === 'string') {
    switch (type) {
      case 'boolean':
        return value !== 'false' && value !== '0' && value !== '';
      case 'number':
        return Number(value);
      case 'json':
        try {
          return JSON.parse(value);
        } catch {
          return value;
        }
      default:
        return value;
    }
  }

  return value;
}

export function camelToKebab(name: string): string {
  return name.replace(/([A-Z])/g, '-$1').toLowerCase();
}

export function collectSlots(host: HTMLElement): Map<string, Element[]> {
  const slots = new Map<string, Element[]>();
  for (const child of Array.from(host.children)) {
    const slotName = child.getAttribute('slot') ?? '';
    const list = slots.get(slotName) ?? [];
    list.push(child);
    slots.set(slotName, list);
  }
  return slots;
}

export function portalNode(node: Node | null): React.ReactElement | null {
  if (!node) return null;
  return React.createElement('span', {
    ref: (el: HTMLSpanElement | null) => {
      if (el && node) {
        el.replaceWith(node);
      }
    },
    style: { display: 'contents' },
  });
}

export function wrapSlotNodes(nodes: Element[]): HTMLDivElement {
  const container = document.createElement('div');
  container.style.display = 'contents';
  for (const node of nodes) {
    container.appendChild(node);
  }
  return container;
}

export function dispatchEvent(
  element: HTMLElement,
  eventName: string,
  detail?: unknown,
): void {
  element.dispatchEvent(
    new CustomEvent(eventName, {
      detail,
      bubbles: true,
      composed: true,
    }),
  );
}

export abstract class BaseCompoundWC extends HTMLElement {
  protected _root: ReturnType<typeof createRoot> | null = null;
  protected _reactContainer: HTMLDivElement | null = null;
  protected _props: Record<string, unknown> = {};
  protected _slotContainers: Map<string, HTMLDivElement> = new Map();
  protected _defaultSlotContainer: HTMLDivElement | null = null;
  protected _initialized = false;

  abstract get propDefs(): PropDefs;
  protected abstract _renderReact(): React.ReactElement;

  connectedCallback() {
    setTimeout(() => this._initialize(), 0);
  }

  disconnectedCallback() {
    this._root?.unmount();
    this._root = null;
    this._reactContainer = null;
  }

  private _initialize() {
    if (this._initialized) return;
    this._initialized = true;

    const slots = collectSlots(this);

    for (const [name, elements] of slots) {
      const container = wrapSlotNodes(elements);
      if (name === '') {
        this._defaultSlotContainer = container;
      } else {
        this._slotContainers.set(name, container);
      }
    }

    while (this.firstChild) this.removeChild(this.firstChild);
    this._reactContainer = document.createElement('div');
    this._reactContainer.style.display = 'contents';
    this.appendChild(this._reactContainer);

    this._root = createRoot(this._reactContainer);
    this._render();
  }

  attributeChangedCallback(_name: string, _oldValue: string | null, _newValue: string | null) {
    this._render();
  }

  protected _getProp(name: string): unknown {
    if (name in this._props) return this._props[name];
    const attrName = camelToKebab(name);
    const type = this.propDefs[name] ?? 'string';
    return parseAttr(this.getAttribute(attrName), type);
  }

  protected _setProp(name: string, value: unknown) {
    const type = this.propDefs[name] ?? 'string';
    this._props[name] = parseValue(value, type);
    this._render();
  }

  protected _readAllProps(): Record<string, unknown> {
    const result: Record<string, unknown> = {};
    for (const [propName, propType] of Object.entries(this.propDefs)) {
      if (propName in this._props) {
        result[propName] = this._props[propName];
      } else {
        const attrName = camelToKebab(propName);
        result[propName] = parseAttr(this.getAttribute(attrName), propType);
      }
    }
    return result;
  }

  protected _getSlot(name: string): HTMLDivElement | null {
    return this._slotContainers.get(name) ?? null;
  }

  protected _getDefaultSlot(): HTMLDivElement | null {
    return this._defaultSlotContainer;
  }

  protected _render() {
    if (!this._root || !this._initialized) return;
    this._root.render(this._renderReact());
  }
}

export function installPropAccessors(
  WCClass: new () => BaseCompoundWC,
  propDefs: PropDefs,
): void {
  for (const propName of Object.keys(propDefs)) {
    Object.defineProperty(WCClass.prototype, propName, {
      get(this: BaseCompoundWC) {
        return this._getProp(propName);
      },
      set(this: BaseCompoundWC, value: unknown) {
        this._setProp(propName, value);
      },
      enumerable: true,
      configurable: true,
    });
  }
}

export function registerWC(
  tagName: string,
  WCClass: CustomElementConstructor,
): void {
  if (typeof customElements !== 'undefined' && !customElements.get(tagName)) {
    customElements.define(tagName, WCClass);
  }
}
