import { FeatureToggle, FeatureToggleOverride } from "../data/entities.js";
import { toggleCreateSchema, toggleUpdateSchema } from "../data/validators.js";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { registerCommand } from "@open-mercato/shared/lib/commands";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { enforceCommandOptimisticLock } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, requireId } from "@open-mercato/shared/lib/commands/helpers";
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import { E } from "../../../generated/entities.ids.generated.js";
function assertGlobalToggleSuperAdmin(ctx) {
  if (ctx.systemActor === true) return;
  if (ctx.auth?.isSuperAdmin !== true) {
    throw new CrudHttpError(403, { error: "Global feature toggles can only be managed by a super administrator." });
  }
}
const featureToggleCrudIndexer = { entityType: E.feature_toggles.feature_toggle };
const FEATURE_TOGGLE_LOCK_RESOURCE_KIND = "feature_toggles.feature_toggle";
function featureToggleIdentifiers(toggle, ctx) {
  return {
    id: toggle.id,
    organizationId: null,
    tenantId: ctx.auth?.tenantId ?? null
  };
}
async function loadToggleSnapshot(em, id) {
  const toggle = await em.findOne(FeatureToggle, { id });
  if (!toggle) return null;
  return {
    id: toggle.id,
    identifier: toggle.identifier,
    name: toggle.name,
    description: toggle.description ?? null,
    category: toggle.category ?? null,
    type: toggle.type ?? "boolean",
    defaultValue: toggle.defaultValue ?? null
  };
}
async function loadOverrideSnapshots(em, toggleId) {
  const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId });
  return overrides.map((o) => ({
    id: o.id,
    toggleId: o.toggle.id,
    tenantId: o.tenantId,
    value: o.value
  }));
}
const createToggleCommand = {
  id: "feature_toggles.global.create",
  async execute(rawInput, ctx) {
    assertGlobalToggleSuperAdmin(ctx);
    const parsed = toggleCreateSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier });
    if (existing) {
      throw new CrudHttpError(400, { error: "Feature toggle identifier already exists" });
    }
    const toggle = em.create(FeatureToggle, {
      identifier: parsed.identifier,
      name: parsed.name,
      description: parsed.description,
      category: parsed.category,
      type: parsed.type,
      defaultValue: parsed.defaultValue
    });
    em.persist(toggle);
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "created",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
    return { toggleId: toggle.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadToggleSnapshot(em, result.toggleId);
  },
  buildLog: async ({ result, ctx }) => {
    const { translate } = await resolveTranslations();
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadToggleSnapshot(em, result.toggleId);
    return {
      actionLabel: translate("feature_toggles.audit.toggles.create", "Create toggle"),
      resourceKind: "feature_toggles.global",
      resourceId: result.toggleId,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const toggleId = logEntry?.resourceId ?? null;
    if (!toggleId) return;
    const em = ctx.container.resolve("em").fork();
    const overrides = await em.find(FeatureToggleOverride, { toggle: toggleId });
    if (overrides.length > 0) {
      em.remove(overrides);
    }
    const toggle = await em.findOne(FeatureToggle, { id: toggleId });
    if (toggle) {
      em.remove(toggle);
      await em.flush();
      const dataEngine = ctx.container.resolve("dataEngine");
      await emitCrudUndoSideEffects({
        dataEngine,
        action: "deleted",
        entity: toggle,
        identifiers: featureToggleIdentifiers(toggle, ctx),
        syncOrigin: ctx.syncOrigin,
        indexer: featureToggleCrudIndexer
      });
      const featureTogglesService = ctx.container.resolve("featureTogglesService");
      await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
    }
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for toggle create" });
    const em = ctx.container.resolve("em").fork();
    let toggle = await em.findOne(FeatureToggle, { id: after.id });
    if (!toggle) {
      toggle = em.create(FeatureToggle, {
        id: after.id,
        identifier: after.identifier,
        name: after.name,
        description: after.description,
        category: after.category,
        type: after.type,
        defaultValue: after.defaultValue
      });
      em.persist(toggle);
    } else {
      toggle.deletedAt = null;
      toggle.identifier = after.identifier;
      toggle.name = after.name;
      toggle.description = after.description;
      toggle.category = after.category;
      toggle.type = after.type;
      toggle.defaultValue = after.defaultValue;
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "created",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
    const featureTogglesService = ctx.container.resolve("featureTogglesService");
    await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
    return { toggleId: toggle.id };
  }
};
const updateToggleCommand = {
  id: "feature_toggles.global.update",
  async prepare(rawInput, ctx) {
    const parsed = toggleUpdateSchema.parse(rawInput);
    const em = ctx.container.resolve("em");
    const snapshot = await loadToggleSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    assertGlobalToggleSuperAdmin(ctx);
    const parsed = toggleUpdateSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const toggle = await em.findOne(FeatureToggle, { id: parsed.id, deletedAt: null });
    if (!toggle) throw new CrudHttpError(404, { error: "Toggle not found" });
    enforceCommandOptimisticLock({
      resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,
      resourceId: toggle.id,
      current: toggle.updatedAt ?? null,
      request: ctx.request ?? null
    });
    const previousIdentifier = toggle.identifier;
    if (parsed.identifier && parsed.identifier !== toggle.identifier) {
      const existing = await em.findOne(FeatureToggle, { identifier: parsed.identifier });
      if (existing && existing.id !== toggle.id) {
        throw new CrudHttpError(400, { error: "Feature toggle identifier already exists" });
      }
    }
    toggle.identifier = parsed.identifier ?? toggle.identifier;
    toggle.name = parsed.name ?? toggle.name;
    toggle.description = parsed.description ?? toggle.description;
    toggle.category = parsed.category ?? toggle.category;
    toggle.type = parsed.type ?? toggle.type;
    toggle.defaultValue = parsed.defaultValue ?? toggle.defaultValue;
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "updated",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
    const featureTogglesService = ctx.container.resolve("featureTogglesService");
    if (previousIdentifier !== toggle.identifier) {
      await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(previousIdentifier);
    }
    await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
    return { toggleId: toggle.id };
  },
  buildLog: async ({ snapshots, ctx }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const em = ctx.container.resolve("em").fork();
    const afterSnapshot = await loadToggleSnapshot(em, before.id);
    const changes = afterSnapshot && before ? buildChanges(
      before,
      afterSnapshot,
      [
        "identifier",
        "name",
        "description",
        "category",
        "failMode",
        "type",
        "defaultValue"
      ]
    ) : {};
    return {
      actionLabel: translate("feature_toggles.audit.toggles.update", "Update toggle"),
      resourceKind: "feature_toggles.global",
      resourceId: before.id,
      snapshotBefore: before,
      snapshotAfter: afterSnapshot ?? null,
      changes,
      payload: {
        undo: {
          before,
          after: afterSnapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    let toggle = await em.findOne(FeatureToggle, { id: before.id });
    if (!toggle) {
      toggle = em.create(FeatureToggle, {
        id: before.id,
        identifier: before.identifier,
        name: before.name,
        description: before.description,
        category: before.category,
        type: before.type,
        defaultValue: before.defaultValue,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(toggle);
    } else {
      toggle.identifier = before.identifier;
      toggle.name = before.name;
      toggle.description = before.description;
      toggle.category = before.category;
      toggle.type = before.type;
      toggle.defaultValue = before.defaultValue;
      toggle.deletedAt = null;
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "updated",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
    const featureTogglesService = ctx.container.resolve("featureTogglesService");
    await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
  }
};
const deleteToggleCommand = {
  id: "feature_toggles.global.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Feature toggle id required");
    const em = ctx.container.resolve("em");
    const snapshot = await loadToggleSnapshot(em, id);
    const overrides = await loadOverrideSnapshots(em, id);
    return snapshot ? { before: snapshot, overrides } : {};
  },
  async execute(input, ctx) {
    assertGlobalToggleSuperAdmin(ctx);
    const id = requireId(input, "Feature toggle id required");
    const em = ctx.container.resolve("em").fork();
    const toggle = await em.findOne(FeatureToggle, { id, deletedAt: null });
    if (!toggle) throw new CrudHttpError(404, { error: "Feature toggle not found" });
    enforceCommandOptimisticLock({
      resourceKind: FEATURE_TOGGLE_LOCK_RESOURCE_KIND,
      resourceId: toggle.id,
      current: toggle.updatedAt ?? null,
      request: ctx.request ?? null
    });
    const featureTogglesService = ctx.container.resolve("featureTogglesService");
    await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
    toggle.deletedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "deleted",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
    return { toggleId: toggle.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    const overrides = snapshots.overrides;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("feature_toggles.audit.toggles.delete", "Delete toggle"),
      resourceKind: "feature_toggles.global",
      resourceId: before.id,
      snapshotBefore: before,
      payload: {
        undo: {
          before,
          overrides
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    const overrides = payload?.overrides || [];
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    let toggle = await em.findOne(FeatureToggle, { id: before.id });
    if (!toggle) {
      toggle = em.create(FeatureToggle, {
        id: before.id,
        identifier: before.identifier,
        name: before.name,
        description: before.description,
        category: before.category,
        type: before.type,
        defaultValue: before.defaultValue,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(toggle);
      for (const ov of overrides) {
        const override = em.create(FeatureToggleOverride, {
          id: ov.id,
          toggle,
          tenantId: ov.tenantId,
          value: ov.value ?? null
        });
        em.persist(override);
      }
    } else {
      toggle.identifier = before.identifier;
      toggle.name = before.name;
      toggle.description = before.description;
      toggle.category = before.category;
      toggle.type = before.type;
      toggle.defaultValue = before.defaultValue;
      toggle.deletedAt = null;
    }
    const featureTogglesService = ctx.container.resolve("featureTogglesService");
    await featureTogglesService.invalidateIsEnabledCacheByIdentifierTag(toggle.identifier);
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "updated",
      entity: toggle,
      identifiers: featureToggleIdentifiers(toggle, ctx),
      syncOrigin: ctx.syncOrigin,
      indexer: featureToggleCrudIndexer
    });
  }
};
registerCommand(createToggleCommand);
registerCommand(updateToggleCommand);
registerCommand(deleteToggleCommand);
//# sourceMappingURL=global.js.map
