import {
  Capabilities,
  Events,
  PluginManager
} from "./chunk-FMN65HSW.mjs";

// src/react/PluginManagerProvider.ts
import { createContext, useContext } from "react";
import { raise } from "@dxos/debug";
var PluginManagerContext = createContext(void 0);
var usePluginManager = () => useContext(PluginManagerContext) ?? raise(new Error("Missing PluginManagerContext"));
var PluginManagerProvider = PluginManagerContext.Provider;

// src/react/useCapabilities.ts
import { useRxValue } from "@effect-rx/rx-react";
import { invariant } from "@dxos/invariant";
var __dxlog_file = "/__w/dxos/dxos/packages/sdk/app-framework/src/react/useCapabilities.ts";
var useCapabilities = (interfaceDef) => {
  const manager = usePluginManager();
  return useRxValue(manager.context.capabilities(interfaceDef));
};
var useCapability = (interfaceDef) => {
  const capabilities = useCapabilities(interfaceDef);
  invariant(capabilities.length > 0, `No capability found for ${interfaceDef.identifier}`, {
    F: __dxlog_file,
    L: 28,
    S: void 0,
    A: [
      "capabilities.length > 0",
      "`No capability found for ${interfaceDef.identifier}`"
    ]
  });
  return capabilities[0];
};

// src/react/common.ts
var useIntentDispatcher = () => useCapability(Capabilities.IntentDispatcher);
var useAppGraph = () => useCapability(Capabilities.AppGraph);
var useLayout = () => useCapability(Capabilities.Layout);

// src/react/ErrorBoundary.tsx
import React, { Component } from "react";
var ErrorBoundary = class extends Component {
  constructor(props) {
    super(props);
    this.state = {
      error: void 0
    };
  }
  static getDerivedStateFromError(error) {
    return {
      error
    };
  }
  componentDidUpdate(prevProps) {
    if (prevProps.data !== this.props.data) {
      this.resetError();
    }
  }
  render() {
    if (this.state.error) {
      return /* @__PURE__ */ React.createElement(this.props.fallback, {
        data: this.props.data,
        error: this.state.error,
        reset: this.resetError
      });
    }
    return this.props.children;
  }
  resetError() {
    this.setState({
      error: void 0
    });
  }
};

// src/react/Surface.tsx
import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
import React2, { memo, forwardRef, Suspense, useMemo, Fragment } from "react";
import { useDefaultValue } from "@dxos/react-hooks";
import { byPosition } from "@dxos/util";
var DEFAULT_PLACEHOLDER = /* @__PURE__ */ React2.createElement(Fragment, null);
var useSurfaces = () => {
  const surfaces = useCapabilities(Capabilities.ReactSurface);
  return useMemo(() => surfaces.flat(), [
    surfaces
  ]);
};
var findCandidates = (surfaces, { role, data }) => {
  return Object.values(surfaces).filter((definition) => Array.isArray(definition.role) ? definition.role.includes(role) : definition.role === role).filter(({ filter }) => filter ? filter(data ?? {}) : true).toSorted(byPosition);
};
var isSurfaceAvailable = (context, { role, data }) => {
  const surfaces = context.getCapabilities(Capabilities.ReactSurface);
  const candidates = findCandidates(surfaces.flat(), {
    role,
    data
  });
  return candidates.length > 0;
};
var Surface = /* @__PURE__ */ memo(/* @__PURE__ */ forwardRef(({ id: _id, role, data: _data, limit, fallback, placeholder = DEFAULT_PLACEHOLDER, ...rest }, forwardedRef) => {
  var _effect = _useSignals();
  try {
    const surfaces = useSurfaces();
    const data = useDefaultValue(_data, () => ({}));
    const definitions = findCandidates(surfaces, {
      role,
      data
    });
    const candidates = limit ? definitions.slice(0, limit) : definitions;
    const nodes = candidates.map(({ component: Component2, id }) => /* @__PURE__ */ React2.createElement(Component2, {
      ref: forwardedRef,
      key: id,
      id,
      role,
      data,
      limit,
      ...rest
    }));
    const suspense = /* @__PURE__ */ React2.createElement(Suspense, {
      fallback: placeholder
    }, nodes);
    return fallback ? /* @__PURE__ */ React2.createElement(ErrorBoundary, {
      data,
      fallback
    }, suspense) : suspense;
  } finally {
    _effect.f();
  }
}));
Surface.displayName = "Surface";

// src/react/useIntentResolver.ts
import { useEffect } from "react";
var useIntentResolver = (module, resolver) => {
  const manager = usePluginManager();
  useEffect(() => {
    manager.context.contributeCapability({
      module,
      interface: Capabilities.IntentResolver,
      implementation: resolver
    });
    return () => manager.context.removeCapability(Capabilities.IntentResolver, resolver);
  }, [
    module,
    resolver
  ]);
};

// src/App.tsx
import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
import { RegistryContext } from "@effect-rx/rx-react";
import { effect } from "@preact/signals-core";
import React3, { useCallback, useEffect as useEffect2, useMemo as useMemo2, useState } from "react";
import { invariant as invariant2 } from "@dxos/invariant";
import { live } from "@dxos/live-object";
import { useDefaultValue as useDefaultValue2 } from "@dxos/react-hooks";

// src/helpers.ts
var topologicalSort = (nodes) => {
  const getDependencies = (nodeId, seen = /* @__PURE__ */ new Set(), path = /* @__PURE__ */ new Set()) => {
    if (path.has(nodeId)) {
      throw new Error(`Circular dependency detected involving ${nodeId}`);
    }
    if (seen.has(nodeId)) {
      return [];
    }
    const node = nodes.find((n) => n.id === nodeId);
    if (!node) {
      throw new Error(`Node ${nodeId} not found but is listed as a dependency`);
    }
    const newPath = /* @__PURE__ */ new Set([
      ...path,
      nodeId
    ]);
    const newSeen = /* @__PURE__ */ new Set([
      ...seen,
      nodeId
    ]);
    const dependsOn = node.dependsOn ?? [];
    return [
      ...dependsOn.flatMap((depId) => getDependencies(depId, newSeen, newPath)),
      nodeId
    ];
  };
  const allDependencies = nodes.map((node) => node.id).flatMap((id) => getDependencies(id)).filter((id, index, self) => self.indexOf(id) === index);
  return allDependencies.map((id) => nodes.find((node) => node.id === id)).filter((node) => node !== void 0);
};

// src/App.tsx
var __dxlog_file2 = "/__w/dxos/dxos/packages/sdk/app-framework/src/App.tsx";
var ENABLED_KEY = "dxos.org/app-framework/enabled";
var useApp = ({ pluginManager, pluginLoader: _pluginLoader, plugins: _plugins, core: _core, defaults: _defaults, placeholder, fallback = DefaultFallback, cacheEnabled = false, safeMode = false }) => {
  const plugins = useDefaultValue2(_plugins, () => []);
  const core = useDefaultValue2(_core, () => plugins.map(({ meta }) => meta.id));
  const defaults = useDefaultValue2(_defaults, () => []);
  const pluginLoader = useMemo2(() => _pluginLoader ?? ((id) => {
    const plugin = plugins.find((plugin2) => plugin2.meta.id === id);
    invariant2(plugin, `Plugin not found: ${id}`, {
      F: __dxlog_file2,
      L: 78,
      S: void 0,
      A: [
        "plugin",
        "`Plugin not found: ${id}`"
      ]
    });
    return plugin;
  }), [
    _pluginLoader,
    plugins
  ]);
  const state = useMemo2(() => live({
    ready: false,
    error: null
  }), []);
  const cached = useMemo2(() => JSON.parse(localStorage.getItem(ENABLED_KEY) ?? "[]"), []);
  const enabled = useMemo2(() => safeMode ? [] : cacheEnabled && cached.length > 0 ? cached : defaults, [
    safeMode,
    cacheEnabled,
    cached,
    defaults
  ]);
  const manager = useMemo2(() => pluginManager ?? new PluginManager({
    pluginLoader,
    plugins,
    core,
    enabled
  }), [
    pluginManager,
    pluginLoader,
    plugins,
    core,
    enabled
  ]);
  useEffect2(() => {
    return manager.activation.on(({ event, state: _state, error }) => {
      if (!state.ready && event === Events.Startup.id) {
        state.ready = _state === "activated";
      }
      if (error && !state.ready && !state.error) {
        state.error = error;
      }
    });
  }, [
    manager,
    state
  ]);
  useEffect2(() => {
    effect(() => {
      cacheEnabled && localStorage.setItem(ENABLED_KEY, JSON.stringify(manager.enabled));
    });
  }, [
    cacheEnabled,
    manager
  ]);
  useEffect2(() => {
    manager.context.contributeCapability({
      interface: Capabilities.PluginManager,
      implementation: manager,
      module: "dxos.org/app-framework/plugin-manager"
    });
    manager.context.contributeCapability({
      interface: Capabilities.RxRegistry,
      implementation: manager.registry,
      module: "dxos.org/app-framework/rx-registry"
    });
    return () => {
      manager.context.removeCapability(Capabilities.PluginManager, manager);
      manager.context.removeCapability(Capabilities.RxRegistry, manager.registry);
    };
  }, [
    manager
  ]);
  useEffect2(() => {
    setupDevtools(manager);
  }, [
    manager
  ]);
  useEffect2(() => {
    const timeout = setTimeout(async () => {
      await Promise.all([
        // TODO(wittjosiah): Factor out such that this could be called per surface role when attempting to render.
        manager.activate(Events.SetupReactSurface),
        manager.activate(Events.Startup)
      ]);
    });
    return () => clearTimeout(timeout);
  }, [
    manager
  ]);
  return useCallback(() => /* @__PURE__ */ React3.createElement(ErrorBoundary, {
    fallback
  }, /* @__PURE__ */ React3.createElement(PluginManagerProvider, {
    value: manager
  }, /* @__PURE__ */ React3.createElement(RegistryContext.Provider, {
    value: manager.registry
  }, /* @__PURE__ */ React3.createElement(App, {
    placeholder,
    state
  })))), [
    fallback,
    manager,
    placeholder,
    state
  ]);
};
var DELAY_PLACEHOLDER = 2e3;
var useLoading = (state) => {
  const [stage, setStage] = useState(0);
  useEffect2(() => {
    const i = setInterval(() => {
      setStage((tick) => {
        switch (tick) {
          case 0:
            if (!state.ready) {
              return 1;
            } else {
              clearInterval(i);
              return 3;
            }
          case 1:
            if (state.ready) {
              return 2;
            }
            break;
          case 2:
            clearInterval(i);
            return 3;
        }
        return tick;
      });
    }, DELAY_PLACEHOLDER);
    return () => clearInterval(i);
  }, []);
  return stage;
};
var App = ({ placeholder: Placeholder, state }) => {
  var _effect = _useSignals2();
  try {
    const reactContexts = useCapabilities(Capabilities.ReactContext);
    const reactRoots = useCapabilities(Capabilities.ReactRoot);
    const stage = useLoading(state);
    if (state.error) {
      throw state.error;
    }
    if (stage < 3) {
      if (!Placeholder) {
        return null;
      }
      return /* @__PURE__ */ React3.createElement(Placeholder, {
        stage
      });
    }
    const ComposedContext = composeContexts(reactContexts);
    return /* @__PURE__ */ React3.createElement(ComposedContext, null, reactRoots.map(({ id, root: Component2 }) => /* @__PURE__ */ React3.createElement(Component2, {
      key: id
    })));
  } finally {
    _effect.f();
  }
};
var DefaultFallback = ({ error }) => {
  var _effect = _useSignals2();
  try {
    return /* @__PURE__ */ React3.createElement("div", {
      style: {
        margin: "0.5rem",
        padding: "1rem",
        overflow: "hidden",
        border: "1px solid red"
      }
    }, /* @__PURE__ */ React3.createElement("h1", {
      style: {
        margin: "0.5rem 0",
        fontSize: "1.2rem"
      }
    }, "[ERROR]: ", error.message), /* @__PURE__ */ React3.createElement("pre", {
      style: {
        overflow: "auto",
        fontSize: "1rem",
        whiteSpace: "pre-wrap",
        color: "#888888"
      }
    }, error.stack));
  } finally {
    _effect.f();
  }
};
var composeContexts = (contexts) => {
  if (contexts.length === 0) {
    return ({ children }) => /* @__PURE__ */ React3.createElement(React3.Fragment, null, children);
  }
  return topologicalSort(contexts).map(({ context }) => context).reduce((Acc, Next) => ({ children }) => /* @__PURE__ */ React3.createElement(Acc, null, /* @__PURE__ */ React3.createElement(Next, null, children)));
};
var setupDevtools = (manager) => {
  globalThis.composer ??= {};
  globalThis.composer.manager = manager;
};

export {
  usePluginManager,
  PluginManagerProvider,
  useCapabilities,
  useCapability,
  useIntentDispatcher,
  useAppGraph,
  useLayout,
  ErrorBoundary,
  useSurfaces,
  isSurfaceAvailable,
  Surface,
  useIntentResolver,
  useApp
};
//# sourceMappingURL=chunk-FO2PH7M3.mjs.map
