import { Context, Deferred, Effect, Option } from "effect";
import { reactive, ref } from "vue";
function createResult(...args) {
  if (args.length === 0) {
    return Option.none();
  }
  return Option.some(args[0]);
}
function createOverlayRuntime() {
  const definitions = reactive({});
  const sessions = ref([]);
  const alerts = ref([]);
  function openAlert(input) {
    return Effect.gen(function* () {
      const deferred = yield* Deferred.make();
      alerts.value.push({
        alertId: crypto.randomUUID(),
        open: true,
        content: input.content,
        icon: input.icon,
        color: input.color,
        deferred
      });
      return yield* Deferred.await(deferred);
    });
  }
  function finalizeCloseAlert(alertId) {
    const index = alerts.value.findIndex((alert2) => alert2.alertId === alertId);
    if (index < 0) {
      return;
    }
    const [alert] = alerts.value.splice(index, 1);
    if (!alert) {
      return;
    }
    Effect.runSync(Deferred.succeed(alert.deferred, alert.pendingResult ?? false));
  }
  function closeAlert(alertId, result, explicit = true) {
    return Effect.sync(() => {
      const alert = alerts.value.find((a) => a.alertId === alertId);
      if (!alert) {
        return;
      }
      if (explicit || alert.pendingResult === void 0) {
        alert.pendingResult = result;
      }
      if (alert.open) {
        alert.open = false;
      }
      if (!alert.finalizeScheduled) {
        alert.finalizeScheduled = true;
        queueMicrotask(() => finalizeCloseAlert(alertId));
      }
    });
  }
  function getDefinition(definitionId) {
    return Reflect.get(definitions, definitionId);
  }
  function getSession(sessionId) {
    return sessions.value.find((session) => session.sessionId === sessionId);
  }
  function removeSession(sessionId) {
    const index = sessions.value.findIndex((session2) => session2.sessionId === sessionId);
    if (index < 0) {
      return void 0;
    }
    const [session] = sessions.value.splice(index, 1);
    return session;
  }
  function finalizeClose(sessionId, result) {
    const session = removeSession(sessionId);
    if (!session) {
      return;
    }
    Effect.runSync(Deferred.succeed(session.deferred, session.pendingResult ?? result ?? Option.none()));
  }
  function requestClose(sessionId, result) {
    const session = getSession(sessionId);
    if (!session || !session.open) {
      return;
    }
    session.open = false;
    session.pendingResult = result;
  }
  function closeDefinitionSessions(definitionId) {
    const targetSessions = sessions.value.filter((session) => session.definitionId === definitionId).map((session) => session.sessionId);
    for (const sessionId of targetSessions) {
      finalizeClose(sessionId, Option.none());
    }
  }
  function registerDefinition(definition) {
    Reflect.set(definitions, definition.definitionId, definition);
  }
  function unregisterDefinition(definitionId, ownerId) {
    const definition = getDefinition(definitionId);
    if (!definition) {
      return;
    }
    if (ownerId !== void 0 && definition.ownerId !== ownerId) {
      return;
    }
    Reflect.deleteProperty(definitions, definitionId);
    closeDefinitionSessions(definitionId);
  }
  function syncDefinitions(ownerId, nextDefinitions) {
    const nextIds = nextDefinitions.map((definition) => definition.definitionId);
    const currentIds = Object.values(definitions).filter((definition) => definition.ownerId === ownerId).map((definition) => definition.definitionId);
    for (const definitionId of currentIds) {
      if (!nextIds.includes(definitionId)) {
        unregisterDefinition(definitionId, ownerId);
      }
    }
    for (const definition of nextDefinitions) {
      registerDefinition(definition);
    }
  }
  function patchSession(sessionId, patch) {
    const session = getSession(sessionId);
    if (!session) {
      return;
    }
    if (patch.shell) {
      session.shell = {
        ...session.shell,
        ...patch.shell,
        descriptionSrOnly: Reflect.has(patch.shell, "description") ? false : session.shell.descriptionSrOnly
      };
    }
    if (patch.props) {
      session.props = {
        ...session.props,
        ...patch.props
      };
    }
  }
  function createHandle(session) {
    return {
      patch: (patch) => Effect.sync(() => {
        patchSession(session.sessionId, patch);
      }),
      close: (...args) => Effect.sync(() => {
        requestClose(session.sessionId, createResult(...args));
      }),
      result: Deferred.await(session.deferred)
    };
  }
  function open(definitionId, options) {
    return Effect.map(Effect.acquireRelease(
      Effect.gen(function* () {
        const definition = getDefinition(definitionId);
        if (!definition) {
          return yield* Effect.fail({
            _tag: "OverlayDefinitionNotFoundError",
            definitionId
          });
        }
        const definitionShell = definition.shell;
        const descriptionSrOnly = options?.shell?.description === void 0 && definitionShell?.description === void 0;
        const deferred = yield* Deferred.make();
        const session = {
          sessionId: crypto.randomUUID(),
          definitionId,
          open: true,
          shell: {
            title: options?.shell?.title ?? definitionShell?.title,
            description: options?.shell?.description ?? definitionShell?.description,
            descriptionSrOnly,
            breakpoint: options?.shell?.breakpoint ?? definitionShell?.breakpoint,
            trap: options?.shell?.trap ?? definitionShell?.trap,
            width: options?.shell?.width ?? definitionShell?.width
          },
          props: {
            ...options?.props
          },
          deferred
        };
        sessions.value.push(session);
        return { handle: createHandle(session), sessionId: session.sessionId };
      }),
      ({ sessionId }) => Effect.sync(() => {
        finalizeClose(sessionId, Option.none());
      })
    ), ({ handle }) => handle);
  }
  const runtime = {
    get definitions() {
      return definitions;
    },
    get sessions() {
      return sessions.value;
    },
    get alerts() {
      return alerts.value;
    },
    get topSession() {
      return sessions.value.at(-1);
    },
    registerDefinition,
    unregisterDefinition,
    syncDefinitions,
    isOpen: (sessionId) => getSession(sessionId)?.open === true,
    open,
    patch: (sessionId, patch) => Effect.sync(() => {
      patchSession(sessionId, patch);
    }),
    close: (sessionId, ...args) => Effect.sync(() => {
      requestClose(sessionId, createResult(...args));
    }),
    closeTop: () => Effect.sync(() => {
      const topSession = sessions.value.at(-1);
      if (topSession) {
        requestClose(topSession.sessionId, Option.none());
      }
    }),
    closeAll: () => Effect.sync(() => {
      const sessionIds = sessions.value.map((session) => session.sessionId);
      for (const sessionId of sessionIds) {
        requestClose(sessionId, Option.none());
      }
    }),
    openAlert,
    closeAlert,
    finalizeClose
  };
  return runtime;
}
const overlayRuntime = createOverlayRuntime();
export function useOverlay() {
  return overlayRuntime;
}
class ActionContext extends Context.Tag("shwfed/ActionContext")() {
}
export { ActionContext };
export function openModal(options) {
  return Effect.gen(function* () {
    const { definitionId } = yield* ActionContext;
    const handle = yield* overlayRuntime.open(definitionId, {
      shell: {
        title: options.title,
        description: options.description,
        trap: options.trap,
        width: options.width
      },
      props: options.props
    });
    if (options.trap) {
      const onBeforeUnload = (event) => {
        event.preventDefault();
      };
      window.addEventListener("beforeunload", onBeforeUnload);
      yield* Effect.addFinalizer(
        () => Effect.sync(() => {
          window.removeEventListener("beforeunload", onBeforeUnload);
        })
      );
    }
    return {
      patch: (props) => handle.patch({ props }),
      // Lets the modal body request its own dismissal (e.g. an in-modal
      // close button reached via a bubbled op-request). Idempotent — a
      // no-op once the session has already been closed.
      close: () => handle.close(),
      modal: handle.result
    };
  });
}
export function reject(input) {
  return Effect.map(overlayRuntime.openAlert(input), (confirmed) => !confirmed);
}
