import { Organization } from "@open-mercato/core/modules/directory/data/entities";
import { isAllOrganizationsSelection } from "@open-mercato/core/modules/directory/constants";
import { parseSelectedOrganizationCookie, parseSelectedTenantCookie } from "./scopeCookies.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("directory").child({ component: "org-scope-cache" });
const ORG_SCOPE_CACHE_KEY_PREFIX = "org-scope";
const ORG_SCOPE_DEFAULT_TTL_MS = 0;
function resolveOrgScopeTtlMs() {
  const raw = process.env.OM_ORG_SCOPE_CACHE_TTL_MS;
  if (raw === void 0) return ORG_SCOPE_DEFAULT_TTL_MS;
  const parsed = Number(raw);
  if (!Number.isFinite(parsed) || parsed < 0) return ORG_SCOPE_DEFAULT_TTL_MS;
  return parsed;
}
function buildOrgScopeCacheKey(parts) {
  const selected = parts.selectedOrgId ?? "none";
  const requested = parts.requestedTenantId ?? "none";
  return `${ORG_SCOPE_CACHE_KEY_PREFIX}:${parts.userId}:${parts.effectiveTenantId}:${selected}:${requested}`;
}
function buildOrgScopeUserCacheTag(userId) {
  return `${ORG_SCOPE_CACHE_KEY_PREFIX}:user:${userId}`;
}
function buildOrgScopeTenantCacheTag(tenantId) {
  return `${ORG_SCOPE_CACHE_KEY_PREFIX}:tenant:${tenantId}`;
}
function buildOrgScopeCacheTags(parts) {
  return [
    buildOrgScopeUserCacheTag(parts.userId),
    buildOrgScopeTenantCacheTag(parts.effectiveTenantId)
  ];
}
function isValidCachedScope(value) {
  if (typeof value !== "object" || value === null) return false;
  const record = value;
  const idOk = (v) => v === null || typeof v === "string";
  const arrOk = (v) => v === null || Array.isArray(v) && v.every((entry) => typeof entry === "string");
  return idOk(record.selectedId) && idOk(record.tenantId) && arrOk(record.filterIds) && arrOk(record.allowedIds);
}
function resolveCacheFromContainer(container) {
  if (!container) return null;
  try {
    const c = container.resolve("cache");
    if (c && typeof c.get === "function" && typeof c.set === "function") return c;
  } catch {
    return null;
  }
  return null;
}
async function invalidateOrganizationScopeCacheForUser(container, userId) {
  const cache = resolveCacheFromContainer(container);
  if (!cache?.deleteByTags) return;
  try {
    await cache.deleteByTags([buildOrgScopeUserCacheTag(userId)]);
  } catch (err) {
    logger.warn("Cache invalidate user failed", { err });
  }
}
async function invalidateOrganizationScopeCacheForTenant(container, tenantId) {
  const cache = resolveCacheFromContainer(container);
  if (!cache?.deleteByTags) return;
  try {
    await cache.deleteByTags([buildOrgScopeTenantCacheTag(tenantId)]);
  } catch (err) {
    logger.warn("Cache invalidate tenant failed", { err });
  }
}
const orgScopeRequestMemo = /* @__PURE__ */ new WeakMap();
function getRequestScopeMemo(request) {
  if (!request || typeof request !== "object" && typeof request !== "function") return null;
  const key = request;
  let memo = orgScopeRequestMemo.get(key);
  if (!memo) {
    memo = /* @__PURE__ */ new Map();
    orgScopeRequestMemo.set(key, memo);
  }
  return memo;
}
function normalizeOrganizationId(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}
function getSelectedOrganizationFromRequest(req) {
  const cookieContainer = req.cookies;
  if (cookieContainer && typeof cookieContainer.get === "function") {
    const val = cookieContainer.get("om_selected_org")?.value;
    return val ?? null;
  }
  const headerContainer = req.headers;
  const header = typeof headerContainer?.get === "function" ? headerContainer.get("cookie") : null;
  return parseSelectedOrganizationCookie(header);
}
function getSelectedTenantFromRequest(req) {
  const cookieContainer = req.cookies;
  if (cookieContainer && typeof cookieContainer.get === "function") {
    const val = cookieContainer.get("om_selected_tenant")?.value;
    return val ?? null;
  }
  const headerContainer = req.headers;
  const header = typeof headerContainer?.get === "function" ? headerContainer.get("cookie") : null;
  return parseSelectedTenantCookie(header);
}
function normalizeOrganizationIds(ids) {
  return Array.from(new Set(
    ids.map((value) => normalizeOrganizationId(value)).filter((value) => {
      if (!value) return false;
      if (isAllOrganizationsSelection(value)) return false;
      return true;
    })
  ));
}
async function loadOrgDescendantMap(em, tenantId, ids) {
  const unique = normalizeOrganizationIds(ids);
  if (!unique.length) return /* @__PURE__ */ new Map();
  const filter = {
    tenant: tenantId,
    id: { $in: unique },
    deletedAt: null
  };
  const orgs = await em.find(Organization, filter);
  const map = /* @__PURE__ */ new Map();
  for (const org of orgs) {
    const id = String(org.id);
    const expansion = [id];
    if (Array.isArray(org.descendantIds)) {
      for (const desc of org.descendantIds) expansion.push(String(desc));
    }
    map.set(id, expansion);
  }
  return map;
}
function expandWithDescendants(map, ids) {
  const set = /* @__PURE__ */ new Set();
  for (const value of ids) {
    const id = normalizeOrganizationId(value);
    if (!id || isAllOrganizationsSelection(id)) continue;
    const expansion = map.get(id);
    if (!expansion) continue;
    for (const entry of expansion) set.add(entry);
  }
  return set;
}
async function resolveOrganizationScope({
  em,
  rbac,
  auth,
  selectedId,
  tenantId: tenantIdOverride
}) {
  if (!auth || !auth.sub) {
    return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null };
  }
  const actorTenantId = typeof auth.tenantId === "string" && auth.tenantId.trim().length > 0 ? auth.tenantId.trim() : null;
  const candidateTenantId = typeof tenantIdOverride === "string" && tenantIdOverride.trim().length > 0 ? tenantIdOverride.trim() : tenantIdOverride === null ? null : actorTenantId;
  if (!candidateTenantId) {
    return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null };
  }
  const usingOverride = candidateTenantId !== actorTenantId;
  const isSuperAdminActor = auth.isSuperAdmin === true;
  const tenantId = usingOverride && actorTenantId && !isSuperAdminActor ? actorTenantId : candidateTenantId;
  if (!tenantId) {
    return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null };
  }
  const normalizedRequestedSelection = normalizeOrganizationId(selectedId);
  const explicitAllOrgsChoice = normalizedRequestedSelection !== null && isAllOrganizationsSelection(normalizedRequestedSelection);
  const normalizedSelectedId = explicitAllOrgsChoice ? null : normalizedRequestedSelection;
  const contextOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null;
  const acl = await rbac.loadAcl(auth.sub, { tenantId, organizationId: contextOrgId });
  const aclIsSuperAdmin = acl?.isSuperAdmin === true;
  const effectiveSuperAdmin = aclIsSuperAdmin || isSuperAdminActor;
  const normalizedAccessible = effectiveSuperAdmin ? null : Array.isArray(acl?.organizations) ? acl.organizations.map((value) => normalizeOrganizationId(value)).filter((value) => value !== null) : null;
  const accessibleList = effectiveSuperAdmin ? null : normalizedAccessible && normalizedAccessible.some((value) => isAllOrganizationsSelection(value)) ? null : normalizedAccessible?.filter((value) => !isAllOrganizationsSelection(value)) ?? null;
  const accountOrgId = actorTenantId && actorTenantId === tenantId ? normalizeOrganizationId(auth.orgId) : null;
  const fallbackOrgId = accountOrgId ?? null;
  const candidateIds = [
    ...accessibleList ?? [],
    ...fallbackOrgId ? [fallbackOrgId] : [],
    ...normalizedSelectedId ? [normalizedSelectedId] : []
  ];
  const orgDescendants = await loadOrgDescendantMap(em, tenantId, candidateIds);
  const loadFallbackSet = () => fallbackOrgId ? expandWithDescendants(orgDescendants, [fallbackOrgId]) : null;
  let allowedSet = null;
  if (accessibleList === null) {
    allowedSet = null;
  } else if (accessibleList.length === 0) {
    allowedSet = /* @__PURE__ */ new Set();
  } else {
    allowedSet = expandWithDescendants(orgDescendants, accessibleList);
  }
  if (allowedSet && allowedSet.size === 0 && fallbackOrgId) {
    const computed = loadFallbackSet();
    if (computed && computed.size > 0) {
      allowedSet = computed;
    }
  }
  const hasUnrestrictedAccess = effectiveSuperAdmin || accessibleList === null;
  const noOrgSelection = normalizedSelectedId === null && !explicitAllOrgsChoice;
  const widenToAllOrgs = explicitAllOrgsChoice && hasUnrestrictedAccess || effectiveSuperAdmin && noOrgSelection;
  const initialSelected = normalizedSelectedId ?? (widenToAllOrgs ? null : accountOrgId ?? null);
  let effectiveSelected = null;
  if (initialSelected) {
    if (allowedSet === null || allowedSet.has(initialSelected)) {
      effectiveSelected = initialSelected;
    }
  }
  let filterSet = null;
  if (effectiveSelected) {
    filterSet = expandWithDescendants(orgDescendants, [effectiveSelected]);
  } else if (allowedSet !== null) {
    filterSet = allowedSet;
  } else if (widenToAllOrgs) {
    filterSet = null;
  } else if (auth.orgId) {
    filterSet = loadFallbackSet();
  }
  if ((!filterSet || filterSet.size === 0) && fallbackOrgId && !widenToAllOrgs) {
    const computed = loadFallbackSet();
    if (computed && computed.size > 0) {
      filterSet = computed;
      if (!effectiveSelected) {
        effectiveSelected = fallbackOrgId;
      }
    }
  }
  return {
    selectedId: effectiveSelected,
    filterIds: filterSet ? Array.from(filterSet) : null,
    allowedIds: allowedSet ? Array.from(allowedSet) : null,
    tenantId
  };
}
async function resolveOrganizationScopeForRequest({
  container,
  auth,
  request,
  selectedId,
  tenantId: tenantOverride
}) {
  if (!auth || !auth.sub) {
    return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null };
  }
  let em = null;
  let rbac = null;
  try {
    em = container.resolve("em");
  } catch {
    em = null;
  }
  try {
    rbac = container.resolve("rbacService");
  } catch {
    rbac = null;
  }
  const normalizeString = (value) => {
    if (typeof value === "string" && value.trim().length > 0) return value.trim();
    return null;
  };
  if (!em || !rbac) {
    const fallbackSelected = normalizeOrganizationId(selectedId ?? auth.orgId ?? null);
    return {
      selectedId: fallbackSelected,
      filterIds: fallbackSelected ? [fallbackSelected] : null,
      allowedIds: fallbackSelected ? [fallbackSelected] : null,
      tenantId: normalizeString(auth.tenantId)
    };
  }
  const actorTenantField = auth.actorTenantId;
  const actorTenant = actorTenantField === void 0 ? normalizeString(auth.tenantId) : actorTenantField === null ? null : normalizeString(actorTenantField);
  const actorOrgField = auth.actorOrgId;
  const actorOrgId = actorOrgField === void 0 ? normalizeString(auth.orgId) : actorOrgField === null ? null : normalizeString(actorOrgField);
  const cookieTenant = request ? getSelectedTenantFromRequest(request) : null;
  const requestedTenant = tenantOverride !== void 0 ? tenantOverride : cookieTenant !== void 0 ? cookieTenant : void 0;
  const requestedTenantId = typeof requestedTenant === "string" && requestedTenant.trim().length > 0 ? requestedTenant.trim() : null;
  const isSuperAdminActor = auth.isSuperAdmin === true;
  let effectiveTenantId = requestedTenantId ?? actorTenant ?? null;
  if (actorTenant && effectiveTenantId && effectiveTenantId !== actorTenant && !isSuperAdminActor) {
    effectiveTenantId = actorTenant;
  }
  if (!effectiveTenantId) {
    return { selectedId: null, filterIds: null, allowedIds: null, tenantId: null };
  }
  const scopedAuth = {
    ...auth,
    tenantId: effectiveTenantId,
    orgId: actorTenant && actorTenant === effectiveTenantId ? actorOrgId ?? null : null
  };
  const rawSelected = selectedId !== void 0 ? selectedId : request ? getSelectedOrganizationFromRequest(request) : null;
  const normalizedSelectedId = typeof rawSelected === "string" && rawSelected.trim().length > 0 ? rawSelected.trim() : null;
  const userId = typeof auth.sub === "string" && auth.sub.length > 0 ? auth.sub : null;
  const ttlMs = resolveOrgScopeTtlMs();
  const cache = ttlMs > 0 ? resolveCacheFromContainer(container) : null;
  const cacheKey = userId ? buildOrgScopeCacheKey({
    userId,
    effectiveTenantId,
    selectedOrgId: normalizedSelectedId,
    requestedTenantId: requestedTenantId ?? null
  }) : null;
  const requestMemo = getRequestScopeMemo(request);
  if (requestMemo && cacheKey) {
    const memoized = requestMemo.get(cacheKey);
    if (memoized) return memoized;
  }
  const resolveScope = async () => {
    if (cache && cacheKey && typeof cache.get === "function") {
      try {
        const cached = await cache.get(cacheKey);
        if (isValidCachedScope(cached)) return cached;
      } catch (err) {
        logger.warn("Cache read failed", { err });
      }
    }
    const baseScope = await resolveOrganizationScope({
      em,
      rbac,
      auth: scopedAuth,
      selectedId: rawSelected,
      tenantId: effectiveTenantId
    });
    if (cache && cacheKey && userId && typeof cache.set === "function") {
      try {
        await cache.set(cacheKey, baseScope, {
          ttl: ttlMs,
          tags: buildOrgScopeCacheTags({ userId, effectiveTenantId })
        });
      } catch (err) {
        logger.warn("Cache write failed", { err });
      }
    }
    return baseScope;
  };
  if (requestMemo && cacheKey) {
    const pending = resolveScope();
    requestMemo.set(cacheKey, pending);
    return pending;
  }
  return resolveScope();
}
async function resolveFeatureCheckContext({
  container,
  auth,
  request,
  selectedId,
  tenantId
}) {
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request, selectedId, tenantId });
  const allowedOrganizationIds = scope.allowedIds ?? null;
  const authOrgId = auth?.orgId ?? null;
  const organizationId = scope.selectedId ?? (authOrgId && (!Array.isArray(allowedOrganizationIds) || allowedOrganizationIds.includes(authOrgId)) ? authOrgId : null) ?? (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length ? allowedOrganizationIds[0] : null);
  return { organizationId, scope, allowedOrganizationIds };
}
export {
  buildOrgScopeTenantCacheTag,
  buildOrgScopeUserCacheTag,
  getSelectedOrganizationFromRequest,
  getSelectedTenantFromRequest,
  invalidateOrganizationScopeCacheForTenant,
  invalidateOrganizationScopeCacheForUser,
  parseSelectedOrganizationCookie,
  parseSelectedTenantCookie,
  resolveFeatureCheckContext,
  resolveOrganizationScope,
  resolveOrganizationScopeForRequest
};
//# sourceMappingURL=organizationScope.js.map
