import { locationService } from '@grafana/runtime';
import { SceneObjectStateChangedEvent } from '../core/events.js';
import { writeSceneLog } from '../utils/writeSceneLog.js';
import { Subscription } from 'rxjs';
import { UniqueUrlKeyMapper } from './UniqueUrlKeyMapper.js';
import { getUrlState, syncStateFromUrl, isUrlValueEqual } from './utils.js';
import { BusEventWithPayload } from '@grafana/data';
import { useMemo } from 'react';

var __typeError = (msg) => {
  throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
var _cache, _location;
class NewSceneObjectAddedEvent extends BusEventWithPayload {
}
NewSceneObjectAddedEvent.type = "new-scene-object-added";
class SceneObjectRemovedEvent extends BusEventWithPayload {
}
SceneObjectRemovedEvent.type = "scene-object-removed";
class UrlSyncManager {
  constructor(_options = {}, locationService$1 = locationService) {
    this._lastSyncedObjectKeys = /* @__PURE__ */ new WeakMap();
    this._options = _options;
    this._locationService = locationService$1;
    this._paramsCache = new UrlParamsCache(locationService$1);
    this._urlKeyMapper = new UniqueUrlKeyMapper({
      namespace: _options.namespace,
      excludeFromNamespace: _options.excludeFromNamespace
    });
  }
  /**
   * Updates the current scene state to match URL state.
   */
  initSync(root) {
    var _a;
    if (this._subs) {
      writeSceneLog("UrlSyncManager", "Unregister previous scene state subscription", (_a = this._sceneRoot) == null ? void 0 : _a.state.key);
      this._subs.unsubscribe();
    }
    writeSceneLog("UrlSyncManager", "init", root.state.key);
    this._sceneRoot = root;
    this._subs = new Subscription();
    this._subs.add(
      root.subscribeToEvent(SceneObjectStateChangedEvent, (evt) => {
        this.handleSceneObjectStateChanged(evt.payload.changedObject);
      })
    );
    this._subs.add(
      root.subscribeToEvent(NewSceneObjectAddedEvent, (evt) => {
        this.handleNewObject(evt.payload);
      })
    );
    this._subs.add(
      root.subscribeToEvent(SceneObjectRemovedEvent, (evt) => {
        this.handleObjectRemoved(evt.payload);
      })
    );
    this._urlKeyMapper.clear();
    this._lastSyncedObjectKeys = /* @__PURE__ */ new WeakMap();
    this._lastLocation = this._locationService.getLocation();
    this.handleNewObject(this._sceneRoot);
    if (this._options.updateUrlOnInit) {
      const urlState = getUrlState(root, this._urlKeyMapper.getOptions());
      if (isUrlStateDifferent(urlState, this._paramsCache.getParams())) {
        this._locationService.partial(urlState, true);
      }
    }
  }
  cleanUp(root) {
    if (this._sceneRoot !== root) {
      return;
    }
    writeSceneLog("UrlSyncManager", "Clean up");
    if (this._subs) {
      this._subs.unsubscribe();
      this._subs = void 0;
      writeSceneLog(
        "UrlSyncManager",
        "Root deactived, unsub to state",
        "same key",
        this._sceneRoot.state.key === root.state.key
      );
    }
    this._sceneRoot = void 0;
    this._lastLocation = void 0;
  }
  handleNewLocation(location) {
    if (!this._sceneRoot || this._lastLocation === location) {
      return;
    }
    writeSceneLog("UrlSyncManager", "handleNewLocation");
    this._lastLocation = location;
    syncStateFromUrl(this._sceneRoot, this._paramsCache.getParams(), this._urlKeyMapper);
  }
  handleNewObject(sceneObj) {
    if (!this._sceneRoot) {
      return;
    }
    syncStateFromUrl(sceneObj, this._paramsCache.getParams(), this._urlKeyMapper);
    this.cacheObjectUrlKeys(sceneObj);
  }
  /**
   * When a scene object is removed from the scene we clean up the URL keys it owned.
   * The removed object may already be detached from the scene tree, so we rely on the
   * unique keys cached while it was synced rather than recomputing them from the tree.
   */
  handleObjectRemoved(sceneObj) {
    if (!this._sceneRoot) {
      return;
    }
    const searchParams = this._locationService.getSearch();
    const mappedUpdated = {};
    const visitNode = (obj) => {
      const keys = this._lastSyncedObjectKeys.get(obj);
      if (keys) {
        for (const uniqueKey of keys) {
          if (searchParams.has(uniqueKey)) {
            mappedUpdated[uniqueKey] = null;
          }
        }
      }
      this._lastSyncedObjectKeys.delete(obj);
      obj.forEachChild(visitNode);
    };
    visitNode(sceneObj);
    if (Object.keys(mappedUpdated).length > 0) {
      writeSceneLog("UrlSyncManager", "handleObjectRemoved, cleaning up URL keys");
      this._locationService.partial(mappedUpdated, true);
      this._lastLocation = this._locationService.getLocation();
    }
  }
  handleSceneObjectStateChanged(changedObject) {
    var _a, _b, _c;
    if (!changedObject.urlSync) {
      return;
    }
    const newUrlState = changedObject.urlSync.getUrlState();
    const searchParams = this._locationService.getSearch();
    const mappedUpdated = {};
    const nextKeys = [];
    for (const key of changedObject.urlSync.getKeys()) {
      const uniqueKey = this._urlKeyMapper.getUniqueKey(key, changedObject);
      nextKeys.push(uniqueKey);
      if (!Object.prototype.hasOwnProperty.call(newUrlState, key)) {
        continue;
      }
      const newUrlValue = newUrlState[key];
      const currentUrlValue = searchParams.getAll(uniqueKey);
      if (!isUrlValueEqual(currentUrlValue, newUrlValue)) {
        mappedUpdated[uniqueKey] = newUrlValue;
      }
    }
    const previousKeys = (_a = this._lastSyncedObjectKeys.get(changedObject)) != null ? _a : [];
    for (const staleKey of previousKeys) {
      if (!nextKeys.includes(staleKey) && searchParams.has(staleKey)) {
        mappedUpdated[staleKey] = null;
      }
    }
    this._lastSyncedObjectKeys.set(changedObject, nextKeys);
    if (Object.keys(mappedUpdated).length > 0) {
      const shouldCreateHistoryEntry = (_c = (_b = changedObject.urlSync).shouldCreateHistoryStep) == null ? void 0 : _c.call(_b, newUrlState);
      const shouldReplace = shouldCreateHistoryEntry !== true;
      writeSceneLog("UrlSyncManager", "onStateChange updating URL");
      this._locationService.partial(mappedUpdated, shouldReplace);
      this._lastLocation = this._locationService.getLocation();
    }
  }
  getUrlState(root) {
    return getUrlState(root, this._urlKeyMapper.getOptions());
  }
  cacheObjectUrlKeys(root) {
    const visitNode = (obj) => {
      if (obj.urlSync) {
        const keys = obj.urlSync.getKeys().map((key) => this._urlKeyMapper.getUniqueKey(key, obj));
        this._lastSyncedObjectKeys.set(obj, keys);
      }
      obj.forEachChild(visitNode);
    };
    visitNode(root);
  }
}
class UrlParamsCache {
  constructor(locationService) {
    this.locationService = locationService;
    __privateAdd(this, _cache);
    __privateAdd(this, _location);
  }
  getParams() {
    const location = this.locationService.getLocation();
    if (__privateGet(this, _location) === location) {
      return __privateGet(this, _cache);
    }
    __privateSet(this, _location, location);
    __privateSet(this, _cache, new URLSearchParams(location.search));
    return __privateGet(this, _cache);
  }
}
_cache = new WeakMap();
_location = new WeakMap();
function isUrlStateDifferent(sceneUrlState, currentParams) {
  for (let key in sceneUrlState) {
    if (!isUrlValueEqual(currentParams.getAll(key), sceneUrlState[key])) {
      return true;
    }
  }
  return false;
}
function useUrlSyncManager(options, locationService) {
  return useMemo(
    () => new UrlSyncManager(
      {
        updateUrlOnInit: options.updateUrlOnInit,
        createBrowserHistorySteps: options.createBrowserHistorySteps,
        namespace: options.namespace,
        excludeFromNamespace: options.excludeFromNamespace
      },
      locationService
    ),
    [
      options.updateUrlOnInit,
      options.createBrowserHistorySteps,
      options.namespace,
      options.excludeFromNamespace,
      locationService
    ]
  );
}

export { NewSceneObjectAddedEvent, SceneObjectRemovedEvent, UrlSyncManager, useUrlSyncManager };
//# sourceMappingURL=UrlSyncManager.js.map
