import { registerCommand } from "@open-mercato/shared/lib/commands";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { buildChanges, emitCrudSideEffects, emitCrudUndoSideEffects, parseWithCustomFields, setCustomFieldsIfAny } from "@open-mercato/shared/lib/commands/helpers";
import { buildCustomFieldResetMap, diffCustomFieldChanges, loadCustomFieldSnapshot } from "@open-mercato/shared/lib/commands/customFieldSnapshots";
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { User } from "@open-mercato/core/modules/auth/data/entities";
import { StaffTeam, StaffTeamMember, StaffTeamRole } from "../data/entities.js";
import {
  staffTeamMemberCreateSchema,
  staffTeamMemberUpdateSchema
} from "../data/validators.js";
import { staffTeamMemberCrudEvents } from "../lib/crud.js";
import {
  applyScopeToWhere,
  commandActorScope,
  commandInputScope,
  ensureOrganizationScope,
  ensureTenantScope,
  extractUndoPayload,
  scopeForDecryption,
  scopedStaffSnapshotWhere,
  staffSnapshotDecryptionScope,
  staffSnapshotScopeFromContext,
  staffSnapshotScopeFromSnapshot
} from "./shared.js";
import { E } from "../../../generated/entities.ids.generated.js";
const teamMemberCrudIndexer = {
  entityType: E.staff.staff_team_member
};
function normalizeStringList(value) {
  if (!Array.isArray(value)) return [];
  const set = /* @__PURE__ */ new Set();
  value.forEach((entry) => {
    if (typeof entry === "string") {
      const trimmed = entry.trim();
      if (trimmed.length) set.add(trimmed);
    }
  });
  return Array.from(set);
}
async function loadTeamMemberSnapshot(em, id, scope) {
  const member = await findOneWithDecryption(
    em,
    StaffTeamMember,
    scopedStaffSnapshotWhere(id, scope),
    void 0,
    staffSnapshotDecryptionScope(scope)
  );
  if (!member) return null;
  return {
    id: member.id,
    tenantId: member.tenantId,
    organizationId: member.organizationId,
    teamId: member.teamId ?? null,
    displayName: member.displayName,
    description: member.description ?? null,
    userId: member.userId ?? null,
    roleIds: Array.isArray(member.roleIds) ? member.roleIds : [],
    tags: Array.isArray(member.tags) ? member.tags : [],
    isActive: member.isActive ?? true,
    deletedAt: member.deletedAt ? member.deletedAt.toISOString() : null
  };
}
async function loadTeamMemberCustomSnapshot(em, snapshot) {
  return loadCustomFieldSnapshot(em, {
    entityId: E.staff.staff_team_member,
    recordId: snapshot.id,
    tenantId: snapshot.tenantId,
    organizationId: snapshot.organizationId
  });
}
function teamMemberSeedFromSnapshot(snapshot) {
  return {
    id: snapshot.id,
    tenantId: snapshot.tenantId,
    organizationId: snapshot.organizationId,
    teamId: snapshot.teamId ?? null,
    displayName: snapshot.displayName,
    description: snapshot.description ?? null,
    userId: snapshot.userId ?? null,
    roleIds: Array.isArray(snapshot.roleIds) ? snapshot.roleIds : [],
    tags: Array.isArray(snapshot.tags) ? snapshot.tags : [],
    availabilityRuleSetId: null,
    isActive: snapshot.isActive,
    createdAt: /* @__PURE__ */ new Date(),
    updatedAt: /* @__PURE__ */ new Date(),
    deletedAt: null
  };
}
const redoTeamMemberCreate = makeCreateRedo({
  entityClass: StaffTeamMember,
  getSnapshotId: (snapshot) => snapshot.id,
  seedFromSnapshot: teamMemberSeedFromSnapshot,
  buildResult: (entity) => ({ memberId: entity.id }),
  events: staffTeamMemberCrudEvents,
  indexer: teamMemberCrudIndexer
});
async function ensureRolesExist(em, roleIds, tenantId, organizationId) {
  if (!roleIds.length) return;
  const roles = await findWithDecryption(
    em,
    StaffTeamRole,
    { id: { $in: roleIds }, tenantId, organizationId, deletedAt: null },
    void 0,
    { tenantId, organizationId }
  );
  if (roles.length !== roleIds.length) {
    throw new CrudHttpError(400, { error: "One or more team roles were not found." });
  }
}
async function ensureUserExists(em, userId, tenantId, organizationId) {
  const user = await findOneWithDecryption(
    em,
    User,
    { id: userId, deletedAt: null },
    void 0,
    { tenantId, organizationId }
  );
  if (!user) throw new CrudHttpError(400, { error: "User not found." });
  if (user.tenantId && user.tenantId !== tenantId) {
    throw new CrudHttpError(400, { error: "User does not belong to this tenant." });
  }
  if (user.organizationId && user.organizationId !== organizationId) {
    throw new CrudHttpError(400, { error: "User does not belong to this organization." });
  }
}
async function ensureTeamExists(em, teamId, tenantId, organizationId) {
  const team = await findOneWithDecryption(
    em,
    StaffTeam,
    { id: teamId, tenantId, organizationId, deletedAt: null },
    void 0,
    { tenantId, organizationId }
  );
  if (!team) throw new CrudHttpError(400, { error: "Team not found." });
}
const createTeamMemberCommand = {
  id: "staff.team-members.create",
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(staffTeamMemberCreateSchema, rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    commandInputScope(ctx, parsed.tenantId, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const roleIds = normalizeStringList(parsed.roleIds);
    const tags = normalizeStringList(parsed.tags);
    if (parsed.userId) {
      await ensureUserExists(em, parsed.userId, parsed.tenantId, parsed.organizationId);
    }
    if (parsed.teamId) {
      await ensureTeamExists(em, parsed.teamId, parsed.tenantId, parsed.organizationId);
    }
    await ensureRolesExist(em, roleIds, parsed.tenantId, parsed.organizationId);
    const now = /* @__PURE__ */ new Date();
    const member = em.create(StaffTeamMember, {
      tenantId: parsed.tenantId,
      organizationId: parsed.organizationId,
      teamId: parsed.teamId ?? null,
      displayName: parsed.displayName,
      description: parsed.description ?? null,
      userId: parsed.userId ?? null,
      roleIds,
      tags,
      availabilityRuleSetId: parsed.availabilityRuleSetId ?? null,
      isActive: parsed.isActive ?? true,
      createdAt: now,
      updatedAt: now,
      deletedAt: null
    });
    em.persist(member);
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await setCustomFieldsIfAny({
      dataEngine: de,
      entityId: E.staff.staff_team_member,
      recordId: member.id,
      tenantId: member.tenantId,
      organizationId: member.organizationId,
      values: custom
    });
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: member,
      identifiers: {
        id: member.id,
        organizationId: member.organizationId,
        tenantId: member.tenantId
      },
      events: staffTeamMemberCrudEvents,
      indexer: teamMemberCrudIndexer
    });
    return { memberId: member.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadTeamMemberSnapshot(em, result.memberId, staffSnapshotScopeFromContext(ctx));
    if (!snapshot) return null;
    const custom = await loadTeamMemberCustomSnapshot(em, snapshot);
    return { snapshot, custom };
  },
  buildLog: async ({ result, ctx }) => {
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadTeamMemberSnapshot(em, result.memberId, staffSnapshotScopeFromContext(ctx));
    if (!snapshot) return null;
    const custom = await loadTeamMemberCustomSnapshot(em, snapshot);
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("staff.audit.teamMembers.create", "Create team member"),
      resourceKind: "staff.teamMember",
      resourceId: snapshot.id,
      tenantId: snapshot.tenantId,
      organizationId: snapshot.organizationId,
      snapshotAfter: snapshot,
      payload: {
        undo: {
          after: snapshot,
          customAfter: custom
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const after = payload?.after;
    if (!after) return;
    const em = ctx.container.resolve("em").fork();
    const member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(after.id, staffSnapshotScopeFromSnapshot(after)));
    if (member) {
      member.deletedAt = /* @__PURE__ */ new Date();
      await em.flush();
      const de = ctx.container.resolve("dataEngine");
      await emitCrudUndoSideEffects({
        dataEngine: de,
        action: "deleted",
        entity: member,
        identifiers: {
          id: member.id,
          organizationId: member.organizationId,
          tenantId: member.tenantId
        },
        events: staffTeamMemberCrudEvents,
        indexer: teamMemberCrudIndexer
      });
    }
  },
  redo: async ({ logEntry, ctx }) => {
    const result = await redoTeamMemberCreate({ input: void 0, ctx, logEntry });
    const payload = extractUndoPayload(logEntry);
    const customAfter = payload?.customAfter;
    if (customAfter && Object.keys(customAfter).length) {
      await setCustomFieldsIfAny({
        dataEngine: ctx.container.resolve("dataEngine"),
        entityId: E.staff.staff_team_member,
        recordId: result.memberId,
        tenantId: payload?.after?.tenantId ?? "",
        organizationId: payload?.after?.organizationId ?? "",
        values: customAfter
      });
    }
    return result;
  }
};
const updateTeamMemberCommand = {
  id: "staff.team-members.update",
  async prepare(rawInput, ctx) {
    const { parsed } = parseWithCustomFields(staffTeamMemberUpdateSchema, rawInput);
    const em = ctx.container.resolve("em");
    const snapshot = await loadTeamMemberSnapshot(em, parsed.id, staffSnapshotScopeFromContext(ctx));
    if (!snapshot) return {};
    const custom = await loadTeamMemberCustomSnapshot(em, snapshot);
    return { before: snapshot, customBefore: custom };
  },
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(staffTeamMemberUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const scope = commandActorScope(ctx);
    const member = await findOneWithDecryption(
      em,
      StaffTeamMember,
      applyScopeToWhere({ id: parsed.id, deletedAt: null }, scope),
      void 0,
      scopeForDecryption(scope)
    );
    if (!member) throw new CrudHttpError(404, { error: "Team member not found." });
    ensureTenantScope(ctx, member.tenantId);
    ensureOrganizationScope(ctx, member.organizationId);
    if (parsed.userId !== void 0 && parsed.userId) {
      await ensureUserExists(em, parsed.userId, member.tenantId, member.organizationId);
    }
    if (parsed.teamId !== void 0 && parsed.teamId) {
      await ensureTeamExists(em, parsed.teamId, member.tenantId, member.organizationId);
    }
    let nextRoleIds;
    if (parsed.roleIds !== void 0) {
      nextRoleIds = normalizeStringList(parsed.roleIds);
      await ensureRolesExist(em, nextRoleIds, member.tenantId, member.organizationId);
    }
    await withAtomicFlush(em, [
      () => {
        if (parsed.userId !== void 0) member.userId = parsed.userId ?? null;
        if (parsed.teamId !== void 0) member.teamId = parsed.teamId ?? null;
        if (parsed.roleIds !== void 0) member.roleIds = nextRoleIds ?? [];
        if (parsed.tags !== void 0) member.tags = normalizeStringList(parsed.tags);
        if (parsed.availabilityRuleSetId !== void 0) member.availabilityRuleSetId = parsed.availabilityRuleSetId ?? null;
        if (parsed.displayName !== void 0) member.displayName = parsed.displayName;
        if (parsed.description !== void 0) member.description = parsed.description ?? null;
        if (parsed.isActive !== void 0) member.isActive = parsed.isActive;
        member.updatedAt = /* @__PURE__ */ new Date();
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await setCustomFieldsIfAny({
      dataEngine: de,
      entityId: E.staff.staff_team_member,
      recordId: member.id,
      tenantId: member.tenantId,
      organizationId: member.organizationId,
      values: custom
    });
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: member,
      identifiers: {
        id: member.id,
        organizationId: member.organizationId,
        tenantId: member.tenantId
      },
      events: staffTeamMemberCrudEvents,
      indexer: teamMemberCrudIndexer
    });
    return { memberId: member.id, updatedAt: member.updatedAt ? member.updatedAt.toISOString() : null };
  },
  buildLog: async ({ snapshots, ctx }) => {
    const before = snapshots.before;
    if (!before) return null;
    const em = ctx.container.resolve("em").fork();
    const after = await loadTeamMemberSnapshot(em, before.id, staffSnapshotScopeFromSnapshot(before));
    if (!after) return null;
    const customBefore = snapshots.customBefore ?? void 0;
    const customAfter = await loadTeamMemberCustomSnapshot(em, after);
    const changes = buildChanges(before, after, [
      "teamId",
      "displayName",
      "description",
      "userId",
      "roleIds",
      "tags",
      "isActive",
      "deletedAt"
    ]);
    const customChanges = diffCustomFieldChanges(customBefore, customAfter);
    if (Object.keys(customChanges).length) {
      changes.customFields = { from: customBefore ?? null, to: customAfter ?? null };
    }
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("staff.audit.teamMembers.update", "Update team member"),
      resourceKind: "staff.teamMember",
      resourceId: before.id,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      snapshotAfter: after,
      changes,
      payload: {
        undo: {
          before,
          after,
          customBefore: customBefore ?? null,
          customAfter
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)));
    if (!member) return;
    member.teamId = before.teamId ?? null;
    member.displayName = before.displayName;
    member.description = before.description ?? null;
    member.userId = before.userId ?? null;
    member.roleIds = Array.isArray(before.roleIds) ? before.roleIds : [];
    member.tags = Array.isArray(before.tags) ? before.tags : [];
    member.isActive = before.isActive;
    member.deletedAt = before.deletedAt ? new Date(before.deletedAt) : null;
    member.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    if (payload.customBefore || payload.customAfter) {
      const reset = buildCustomFieldResetMap(payload.customBefore ?? void 0, payload.customAfter ?? void 0);
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: E.staff.staff_team_member,
        recordId: member.id,
        tenantId: member.tenantId,
        organizationId: member.organizationId,
        values: reset
      });
    }
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: member,
      identifiers: {
        id: member.id,
        organizationId: member.organizationId,
        tenantId: member.tenantId
      },
      events: staffTeamMemberCrudEvents,
      indexer: teamMemberCrudIndexer
    });
  }
};
const deleteTeamMemberCommand = {
  id: "staff.team-members.delete",
  async prepare(input, ctx) {
    const id = input?.id;
    if (!id) throw new CrudHttpError(400, { error: "Member id is required." });
    const em = ctx.container.resolve("em");
    const snapshot = await loadTeamMemberSnapshot(em, id, staffSnapshotScopeFromContext(ctx));
    if (!snapshot) return {};
    const custom = await loadTeamMemberCustomSnapshot(em, snapshot);
    return { before: snapshot, customBefore: custom };
  },
  async execute(input, ctx) {
    const id = input?.id;
    if (!id) throw new CrudHttpError(400, { error: "Member id is required." });
    const em = ctx.container.resolve("em").fork();
    const scope = commandActorScope(ctx);
    const member = await findOneWithDecryption(
      em,
      StaffTeamMember,
      applyScopeToWhere({ id, deletedAt: null }, scope),
      void 0,
      scopeForDecryption(scope)
    );
    if (!member) throw new CrudHttpError(404, { error: "Team member not found." });
    ensureTenantScope(ctx, member.tenantId);
    ensureOrganizationScope(ctx, member.organizationId);
    member.deletedAt = /* @__PURE__ */ new Date();
    member.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: member,
      identifiers: {
        id: member.id,
        organizationId: member.organizationId,
        tenantId: member.tenantId
      },
      events: staffTeamMemberCrudEvents,
      indexer: teamMemberCrudIndexer
    });
    return { memberId: member.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const customBefore = snapshots.customBefore ?? void 0;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("staff.audit.teamMembers.delete", "Delete team member"),
      resourceKind: "staff.teamMember",
      resourceId: before.id,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      payload: {
        undo: {
          before,
          customBefore: customBefore ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    let member = await em.findOne(StaffTeamMember, scopedStaffSnapshotWhere(before.id, staffSnapshotScopeFromSnapshot(before)));
    if (!member) {
      member = em.create(StaffTeamMember, {
        id: before.id,
        tenantId: before.tenantId,
        organizationId: before.organizationId,
        teamId: before.teamId ?? null,
        displayName: before.displayName,
        description: before.description ?? null,
        userId: before.userId ?? null,
        roleIds: before.roleIds ?? [],
        tags: before.tags ?? [],
        isActive: before.isActive,
        deletedAt: null,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(member);
    } else {
      member.teamId = before.teamId ?? null;
      member.displayName = before.displayName;
      member.description = before.description ?? null;
      member.userId = before.userId ?? null;
      member.roleIds = before.roleIds ?? [];
      member.tags = before.tags ?? [];
      member.isActive = before.isActive;
      member.deletedAt = null;
      member.updatedAt = /* @__PURE__ */ new Date();
    }
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    if (payload.customBefore) {
      const reset = buildCustomFieldResetMap(payload.customBefore, void 0);
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: E.staff.staff_team_member,
        recordId: member.id,
        tenantId: member.tenantId,
        organizationId: member.organizationId,
        values: reset
      });
    }
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "created",
      entity: member,
      identifiers: {
        id: member.id,
        organizationId: member.organizationId,
        tenantId: member.tenantId
      },
      events: staffTeamMemberCrudEvents,
      indexer: teamMemberCrudIndexer
    });
  }
};
registerCommand(createTeamMemberCommand);
registerCommand(updateTeamMemberCommand);
registerCommand(deleteTeamMemberCommand);
//# sourceMappingURL=team-members.js.map
