import JoanGlyphEngine from "./joan-engine.js";

export const JOAN_GLYPH_TAG_NAME = "joan-glyph";

const observed = [
  "sprite",
  "seed",
  "resolution",
  "speed",
  "density",
  "field",
  "pixel-shape",
  "pixel-switch",
  "orb-boundary",
  "orb-background-color",
  "orb-background-mode",
  "non-error-background",
  "non-error-off",
  "non-error-ink",
  "non-error-accent",
  "non-error-glow",
  "noninteractive",
  "paused",
  "transparent",
];

const paletteAttributes = {
  "non-error-background": "background",
  "non-error-off": "off",
  "non-error-ink": "ink",
  "non-error-accent": "accent",
  "non-error-glow": "glow",
};

// Keeping the module evaluable without DOM globals lets SSR builds inspect and
// bundle it. Instances are still browser elements and should only be created
// after a real HTMLElement implementation exists.
const HTMLElementBase =
  typeof globalThis.HTMLElement === "undefined"
    ? class JoanGlyphSsrBase extends EventTarget {}
    : globalThis.HTMLElement;

export class JoanGlyphElement extends HTMLElementBase {
  static observedAttributes = observed;

  constructor() {
    super();
    const root = this.attachShadow({ mode: "open" });
    const style = document.createElement("style");
    style.textContent = `
      :host {
        display: inline-block;
        inline-size: 68px;
        block-size: 68px;
        aspect-ratio: 1;
        contain: layout paint style;
      }
      canvas {
        display: block;
        inline-size: 100%;
        block-size: 100%;
        image-rendering: pixelated;
        touch-action: none;
      }
    `;
    this.canvas = document.createElement("canvas");
    root.append(style, this.canvas);
  }

  connectedCallback() {
    if (this._engine) return;
    this._engine = new JoanGlyphEngine(this.canvas, this.readOptions());
  }

  disconnectedCallback() {
    this._engine?.destroy();
    this._engine = null;
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (!this._engine || oldValue === newValue) return;
    switch (name) {
      case "sprite":
        this._engine.setSprite(newValue || "ai.idle");
        break;
      case "seed":
        this._engine.setSeed(newValue || "joan-v5");
        break;
      case "resolution":
        this._engine.setResolution(Number(newValue) || 68);
        break;
      case "field":
        if (newValue === null) this._engine.useRecipeFields();
        else this._engine.setField(newValue);
        break;
      case "pixel-shape":
        if (newValue === null) this._engine.setOptions({ pixelShape: null });
        else this._engine.setPixelShape(newValue);
        break;
      case "pixel-switch":
        if (newValue === null) this._engine.setOptions({ pixelSwitch: null });
        else this._engine.setPixelSwitch(newValue);
        break;
      case "orb-boundary":
        this._engine.setOptions({
          orbBoundary:
            String(newValue || "").trim().toLowerCase() === "gestalt"
              ? "gestalt"
              : "defined",
        });
        break;
      case "orb-background-color":
      case "orb-background-mode":
        this._engine.setOptions(this.readOrbBackground());
        break;
      case "non-error-background":
      case "non-error-off":
      case "non-error-ink":
      case "non-error-accent":
      case "non-error-glow":
        this._engine.setNonErrorPalette(this.readNonErrorPalette());
        break;
      case "paused":
        if (newValue === null) this._engine.play();
        else this._engine.pause();
        break;
      case "noninteractive":
        this._engine.setOptions({ interactive: newValue === null });
        break;
      case "transparent":
        this._engine.setOptions({ background: newValue === null });
        break;
      case "speed":
        this._engine.setOptions({ speed: Number(newValue) || 1 });
        break;
      case "density":
        this._engine.setOptions({ density: Number(newValue) || 1 });
        break;
      default:
        break;
    }
  }

  readOptions() {
    return {
      sprite: this.getAttribute("sprite") || "ai.idle",
      seed: this.getAttribute("seed") || "joan-v5",
      gridSize: Number(this.getAttribute("resolution")) || 68,
      speed: Number(this.getAttribute("speed")) || 1,
      density: Number(this.getAttribute("density")) || 1,
      field: this.getAttribute("field") || undefined,
      pixelShape: this.getAttribute("pixel-shape") || undefined,
      pixelSwitch: this.getAttribute("pixel-switch") || undefined,
      orbBoundary:
        String(this.getAttribute("orb-boundary") || "").trim().toLowerCase() ===
        "gestalt"
          ? "gestalt"
          : "defined",
      ...this.readOrbBackground(),
      nonErrorPalette: this.readNonErrorPalette(),
      autoplay: !this.hasAttribute("paused"),
      interactive: !this.hasAttribute("noninteractive"),
      background: !this.hasAttribute("transparent"),
    };
  }

  readOrbBackground() {
    const colorAttribute = this.getAttribute("orb-background-color")?.trim();
    const orbBackgroundColor =
      colorAttribute && colorAttribute.toLowerCase() !== "transparent"
        ? colorAttribute
        : null;
    const modeAttribute = this.getAttribute("orb-background-mode");
    const requestedMode = String(modeAttribute || "").trim().toLowerCase();
    const orbBackgroundMode =
      modeAttribute === null
        ? orbBackgroundColor
          ? "solid"
          : "none"
        : ["none", "solid", "pixelated"].includes(requestedMode)
          ? requestedMode
          : "none";

    return { orbBackgroundColor, orbBackgroundMode };
  }

  readNonErrorPalette() {
    const palette = {};
    for (const [attribute, key] of Object.entries(paletteAttributes)) {
      const value = this.getAttribute(attribute)?.trim();
      if (value) palette[key] = value;
    }
    return Object.keys(palette).length ? palette : null;
  }

  get engine() {
    return this._engine;
  }

  setSprite(sprite, options) {
    return this._engine?.setSprite(sprite, options);
  }

  setProgress(value) {
    return this._engine?.setProgress(value);
  }

  setNonErrorPalette(palette) {
    return this._engine?.setNonErrorPalette(palette);
  }

  setSeed(seed) {
    return this._engine?.setSeed(seed);
  }

  setResolution(size) {
    return this._engine?.setResolution(size);
  }

  setField(field) {
    return this._engine?.setField(field);
  }

  useRecipeFields() {
    return this._engine?.useRecipeFields();
  }

  setPixelShape(shape) {
    return this._engine?.setPixelShape(shape);
  }

  setPixelSwitch(pixelSwitch) {
    return this._engine?.setPixelSwitch(pixelSwitch);
  }

  setOptions(options) {
    return this._engine?.setOptions(options);
  }

  configure(options, configureOptions) {
    return this._engine?.configure(options, configureOptions);
  }

  setAudioLevel(value) {
    return this._engine?.setAudioLevel(value);
  }

  activate(options) {
    return this._engine?.activate(options);
  }

  resume(options) {
    return this._engine?.resume(options);
  }

  play() {
    return this._engine?.play();
  }

  pause() {
    return this._engine?.pause();
  }

  toggle() {
    return this._engine?.toggle();
  }

  renderOnce(time) {
    return this._engine?.renderOnce(time);
  }

  transitionTo(sprite, options) {
    return this._engine?.transitionTo(sprite, options);
  }

  whenTransitionComplete(options) {
    return this._engine?.whenTransitionComplete(options);
  }

  inspect() {
    return this._engine?.inspect();
  }

  on(type, listener, options) {
    return this._engine?.on(type, listener, options);
  }

  exportConfig() {
    return this._engine?.exportConfig();
  }

  signal(type, payload) {
    return this._engine?.signal(type, payload);
  }
}

export function defineJoanGlyph(tagName = JOAN_GLYPH_TAG_NAME) {
  const registry = globalThis.customElements;
  if (!registry) return JoanGlyphElement;
  if (!registry.get(tagName)) registry.define(tagName, JoanGlyphElement);
  return JoanGlyphElement;
}

// Preserve the original import-and-register behavior. Applications that prefer
// an explicit side-effect entry can import `web-component/register` instead.
if (typeof globalThis.customElements !== "undefined") defineJoanGlyph();

export default JoanGlyphElement;
