import { unsafeCSS, LitElement, html } from "lit";
import { property, state, queryAssignedElements } from "lit/decorators.js";
import { customElement } from "../../decorator.js";
import componentStyle from "./poi-controller.css.js";
import "../poi-layer-stack/poi-layer-stack.js";
import { ObcPoiButtonType, resolvePoiButtonTypeFromBoxSize } from "../building-blocks/poi-button/poi-button.js";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
  var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
    if (decorator = decorators[i])
      result = (kind ? decorator(target, key, result) : decorator(result)) || result;
  if (kind && result) __defProp(target, key, result);
  return result;
};
var PoiFitMode = /* @__PURE__ */ ((PoiFitMode2) => {
  PoiFitMode2["Contain"] = "contain";
  PoiFitMode2["Cover"] = "cover";
  return PoiFitMode2;
})(PoiFitMode || {});
let ObcPoiController = class extends LitElement {
  constructor() {
    super(...arguments);
    this.fit = "contain";
    this.frames = null;
    this.detections = null;
    this.frameIndex = null;
    this.confidenceMin = null;
    this.classFilter = null;
    this.keyFn = null;
    this.mediaWidth = 0;
    this.mediaHeight = 0;
    this.renderWidth = 0;
    this.renderHeight = 0;
    this.handleMediaSlotChange = () => this.setupMediaObservers();
    this.handleStackSlotChange = () => this.requestUpdate();
    this.controllerTargets = /* @__PURE__ */ new Map();
    this.currentMedia = null;
    this.mediaHandlers = {
      loadedmetadata: () => {
        if (this.currentMedia) this.updateMediaMetrics(this.currentMedia);
      },
      timeupdate: () => this.requestUpdate(),
      seeked: () => this.requestUpdate(),
      load: () => {
        if (this.currentMedia) this.updateMediaMetrics(this.currentMedia);
      }
    };
  }
  firstUpdated() {
    this.setupMediaObservers();
    const slot = this.shadowRoot?.querySelector('slot[name="media"]');
    slot?.addEventListener("slotchange", this.handleMediaSlotChange);
    const stackSlot = this.shadowRoot?.querySelector('slot[name="stack"]');
    stackSlot?.addEventListener("slotchange", this.handleStackSlotChange);
  }
  updated(_changed) {
    this.syncTargetsToCustomStack();
  }
  disconnectedCallback() {
    super.disconnectedCallback();
    this.resizeObserver?.disconnect();
    this.resizeObserver = void 0;
    this.detachMediaListeners();
    this.controllerTargets.clear();
    const slot = this.shadowRoot?.querySelector('slot[name="media"]');
    slot?.removeEventListener("slotchange", this.handleMediaSlotChange);
    const stackSlot = this.shadowRoot?.querySelector('slot[name="stack"]');
    stackSlot?.removeEventListener("slotchange", this.handleStackSlotChange);
  }
  setupMediaObservers() {
    this.resizeObserver?.disconnect();
    this.resizeObserver = void 0;
    const media = this.getMediaElement();
    if (!media) return;
    if (this.currentMedia && this.currentMedia !== media) {
      this.detachMediaListeners();
    }
    this.currentMedia = media;
    this.updateMediaMetrics(media);
    if (media instanceof HTMLVideoElement) {
      media.addEventListener(
        "loadedmetadata",
        this.mediaHandlers.loadedmetadata
      );
      media.addEventListener("timeupdate", this.mediaHandlers.timeupdate);
      media.addEventListener("seeked", this.mediaHandlers.seeked);
    } else if (media instanceof HTMLImageElement) {
      if (!media.complete) {
        media.addEventListener("load", this.mediaHandlers.load);
      }
    }
    this.resizeObserver = new ResizeObserver(() => {
      this.updateMediaMetrics(media);
    });
    this.resizeObserver.observe(media);
  }
  detachMediaListeners() {
    if (!this.currentMedia) return;
    if (this.currentMedia instanceof HTMLVideoElement) {
      this.currentMedia.removeEventListener(
        "loadedmetadata",
        this.mediaHandlers.loadedmetadata
      );
      this.currentMedia.removeEventListener(
        "timeupdate",
        this.mediaHandlers.timeupdate
      );
      this.currentMedia.removeEventListener(
        "seeked",
        this.mediaHandlers.seeked
      );
    } else if (this.currentMedia instanceof HTMLImageElement) {
      this.currentMedia.removeEventListener("load", this.mediaHandlers.load);
    }
    this.currentMedia = null;
  }
  getMediaElement() {
    const el = this.mediaElements?.[0];
    if (el instanceof HTMLVideoElement || el instanceof HTMLImageElement) {
      return el;
    }
    return null;
  }
  getStackElement() {
    const el = this.stackElements?.[0];
    if (el instanceof HTMLElement) return el;
    return null;
  }
  updateMediaMetrics(media) {
    const rect = media.getBoundingClientRect();
    this.renderWidth = rect.width;
    this.renderHeight = rect.height;
    if (media instanceof HTMLVideoElement) {
      this.mediaWidth = media.videoWidth || this.mediaWidth;
      this.mediaHeight = media.videoHeight || this.mediaHeight;
    } else {
      this.mediaWidth = media.naturalWidth || this.mediaWidth;
      this.mediaHeight = media.naturalHeight || this.mediaHeight;
    }
  }
  getActiveDetections() {
    if (Array.isArray(this.detections)) {
      return this.filterDetections(this.detections);
    }
    if (!Array.isArray(this.frames) || this.frames.length === 0) return [];
    const frame = this.pickFrame();
    if (!frame) return [];
    return this.filterDetections(frame.detections ?? []);
  }
  filterDetections(detections) {
    let filtered = detections;
    if (this.confidenceMin !== null && this.confidenceMin !== void 0) {
      filtered = filtered.filter(
        (det) => det.confidence === void 0 || det.confidence >= this.confidenceMin
      );
    }
    if (Array.isArray(this.classFilter) && this.classFilter.length > 0) {
      filtered = filtered.filter(
        (det) => det.class !== void 0 && this.classFilter.includes(det.class)
      );
    }
    return filtered;
  }
  pickFrame() {
    const frames = this.frames ?? [];
    if (frames.length === 0) return null;
    const media = this.getMediaElement();
    if (this.frameIndex !== null && this.frameIndex !== void 0) {
      return frames[this.frameIndex] ?? frames[frames.length - 1] ?? null;
    }
    if (media instanceof HTMLVideoElement) {
      const time = media.currentTime;
      let best = frames[0] ?? null;
      let bestDelta = Number.POSITIVE_INFINITY;
      for (const frame of frames) {
        if (typeof frame.timestamp !== "number") continue;
        const delta = Math.abs(frame.timestamp - time);
        if (delta < bestDelta) {
          best = frame;
          bestDelta = delta;
        }
      }
      return best;
    }
    return frames[0] ?? null;
  }
  mapDetection(det) {
    if (!Number.isFinite(this.mediaWidth) || !Number.isFinite(this.mediaHeight) || !Number.isFinite(this.renderWidth) || !Number.isFinite(this.renderHeight) || this.mediaWidth === 0 || this.mediaHeight === 0 || this.renderWidth === 0 || this.renderHeight === 0) {
      return null;
    }
    const scale = this.fit === "cover" ? Math.max(
      this.renderWidth / this.mediaWidth,
      this.renderHeight / this.mediaHeight
    ) : Math.min(
      this.renderWidth / this.mediaWidth,
      this.renderHeight / this.mediaHeight
    );
    const contentWidth = this.mediaWidth * scale;
    const contentHeight = this.mediaHeight * scale;
    const offsetX = (this.renderWidth - contentWidth) / 2;
    const offsetY = (this.renderHeight - contentHeight) / 2;
    return {
      x: offsetX + det.x * scale,
      y: offsetY + det.y * scale,
      scale
    };
  }
  syncTargetsToCustomStack() {
    const clearTargets = () => {
      if (this.controllerTargets.size === 0) {
        return;
      }
      this.controllerTargets.forEach((target) => target.remove());
      this.controllerTargets.clear();
    };
    const stack = this.getStackElement();
    if (!stack) {
      clearTargets();
      return;
    }
    const explicitLayer = stack.querySelector(
      'obc-poi-layer[data-controller-layer="background"]'
    );
    const layers = Array.from(
      stack.querySelectorAll("obc-poi-layer")
    );
    const isSelectedLayer = (candidate) => candidate.isSelected === true || candidate.hasAttribute("is-selected");
    const firstNonSelectedLayer = layers.find(
      (candidate) => !isSelectedLayer(candidate)
    );
    const layer = explicitLayer ?? firstNonSelectedLayer ?? layers[layers.length - 1] ?? null;
    if (!layer) {
      clearTargets();
      return;
    }
    const active = this.getActiveDetections();
    const activeKeys = /* @__PURE__ */ new Set();
    active.forEach((det, index) => {
      const mapped = this.mapDetection(det);
      if (!mapped) return;
      const key = det.id ?? (this.keyFn ? this.keyFn(det, index) : `index:${index}`);
      activeKeys.add(key);
      let target = this.controllerTargets.get(key);
      if (!target) {
        target = document.createElement("obc-poi-data");
        target.dataset.controller = "1";
        target.dataset.detectionIndex = String(index);
        target.buttonType = ObcPoiButtonType.Button;
        target.fixedTarget = false;
        this.controllerTargets.set(key, target);
      }
      if (!target.isConnected || target.parentElement !== layer) {
        layer.appendChild(target);
      }
      target.dataset.detectionIndex = String(index);
      target.x = mapped.x;
      target.y = mapped.y;
      const boxWidth = typeof det.box_width === "number" && Number.isFinite(det.box_width) ? det.box_width * mapped.scale : null;
      const boxHeight = typeof det.box_height === "number" && Number.isFinite(det.box_height) ? det.box_height * mapped.scale : null;
      target.buttonType = resolvePoiButtonTypeFromBoxSize(boxWidth, boxHeight);
    });
    Array.from(this.controllerTargets.entries()).forEach(([key, target]) => {
      if (!activeKeys.has(key)) {
        target.remove();
        this.controllerTargets.delete(key);
      }
    });
  }
  render() {
    return html`
      <div class="wrapper">
        <div class="media">
          <slot name="media"></slot>
        </div>
        <div class="overlay">
          <slot name="stack"></slot>
        </div>
      </div>
    `;
  }
};
ObcPoiController.styles = unsafeCSS(componentStyle);
__decorateClass([
  property({ type: String })
], ObcPoiController.prototype, "fit", 2);
__decorateClass([
  property({ type: Array })
], ObcPoiController.prototype, "frames", 2);
__decorateClass([
  property({ type: Array })
], ObcPoiController.prototype, "detections", 2);
__decorateClass([
  property({ type: Number })
], ObcPoiController.prototype, "frameIndex", 2);
__decorateClass([
  property({ type: Number })
], ObcPoiController.prototype, "confidenceMin", 2);
__decorateClass([
  property({ type: Array })
], ObcPoiController.prototype, "classFilter", 2);
__decorateClass([
  property({ attribute: false })
], ObcPoiController.prototype, "keyFn", 2);
__decorateClass([
  state()
], ObcPoiController.prototype, "mediaWidth", 2);
__decorateClass([
  state()
], ObcPoiController.prototype, "mediaHeight", 2);
__decorateClass([
  state()
], ObcPoiController.prototype, "renderWidth", 2);
__decorateClass([
  state()
], ObcPoiController.prototype, "renderHeight", 2);
__decorateClass([
  queryAssignedElements({ slot: "media", flatten: true })
], ObcPoiController.prototype, "mediaElements", 2);
__decorateClass([
  queryAssignedElements({ slot: "stack", flatten: true })
], ObcPoiController.prototype, "stackElements", 2);
ObcPoiController = __decorateClass([
  customElement("obc-poi-controller")
], ObcPoiController);
export {
  ObcPoiController,
  PoiFitMode
};
//# sourceMappingURL=poi-controller.js.map
