import { NextResponse } from "next/server";
import { z } from "zod";
import { logCrudAccess, makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { Organization, Tenant } from "@open-mercato/core/modules/directory/data/entities";
import { organizationCreateSchema, organizationUpdateSchema } from "@open-mercato/core/modules/directory/data/validators";
import {
  computeHierarchyForOrganizations
} from "@open-mercato/core/modules/directory/lib/hierarchy";
import { isAllOrganizationsSelection } from "@open-mercato/core/modules/directory/constants";
import {
  getSelectedOrganizationFromRequest,
  resolveOrganizationScopeForRequest
} from "@open-mercato/core/modules/directory/utils/organizationScope";
import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
import { E } from "../../../../generated/entities.ids.generated.js";
import { organizationCrudEvents, organizationCrudIndexer } from "@open-mercato/core/modules/directory/commands/organizations";
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
import {
  directoryTag,
  directoryErrorSchema,
  directoryOkSchema,
  organizationListResponseSchema
} from "../openapi.js";
import { resolveIsSuperAdmin, enforceTenantSelection } from "@open-mercato/core/modules/auth/lib/tenantAccess";
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
const rawBodySchema = z.object({}).passthrough();
const viewSchema = z.object({
  page: z.coerce.number().min(1).default(1),
  pageSize: z.coerce.number().min(1).max(200).default(50),
  search: z.string().optional(),
  view: z.enum(["options", "manage", "tree"]).default("options"),
  ids: z.string().optional(),
  tenantId: z.string().uuid().optional(),
  includeInactive: z.enum(["true", "false"]).optional(),
  status: z.enum(["all", "active", "inactive"]).optional()
});
function parseIds(raw) {
  if (!raw) return null;
  const ids = raw.split(",").map((s) => s.trim()).filter(Boolean);
  return ids.length ? Array.from(new Set(ids)) : null;
}
function stringId(value) {
  return String(value);
}
function enforceTenantScope(authTenantId, requestedTenantId, isSuperAdmin) {
  if (isSuperAdmin) {
    return requestedTenantId || authTenantId || null;
  }
  if (authTenantId && requestedTenantId && requestedTenantId !== authTenantId) {
    return null;
  }
  return requestedTenantId || authTenantId;
}
const crud = makeCrudRoute({
  metadata: {
    GET: { requireAuth: true, requireFeatures: ["directory.organizations.view"] },
    POST: { requireAuth: true, requireFeatures: ["directory.organizations.manage"] },
    PUT: { requireAuth: true, requireFeatures: ["directory.organizations.manage"] },
    DELETE: { requireAuth: true, requireFeatures: ["directory.organizations.manage"] }
  },
  orm: {
    entity: Organization,
    idField: "id",
    orgField: null,
    tenantField: null,
    softDeleteField: "deletedAt"
  },
  events: organizationCrudEvents,
  indexer: organizationCrudIndexer,
  actions: {
    create: {
      commandId: "directory.organizations.create",
      schema: rawBodySchema,
      mapInput: ({ parsed }) => parsed,
      response: ({ result }) => ({ id: String(result.id) }),
      status: 201
    },
    update: {
      commandId: "directory.organizations.update",
      schema: rawBodySchema,
      mapInput: ({ parsed }) => parsed,
      response: () => ({ ok: true })
    },
    delete: {
      commandId: "directory.organizations.delete",
      response: () => ({ ok: true })
    }
  }
});
const metadata = crud.metadata;
async function GET(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth) return NextResponse.json({ items: [] }, { status: 401 });
  const url = new URL(req.url);
  const parsed = viewSchema.safeParse({
    page: url.searchParams.get("page") ?? void 0,
    pageSize: url.searchParams.get("pageSize") ?? void 0,
    search: url.searchParams.get("search") ?? void 0,
    view: url.searchParams.get("view") ?? void 0,
    ids: url.searchParams.get("ids") ?? void 0,
    tenantId: url.searchParams.get("tenantId") ?? void 0,
    includeInactive: url.searchParams.get("includeInactive") ?? void 0,
    status: url.searchParams.get("status") ?? void 0
  });
  if (!parsed.success) return NextResponse.json({ items: [] }, { status: 400 });
  const query = parsed.data;
  const ids = parseIds(query.ids ?? null);
  const requestedTenantRaw = query.tenantId ?? null;
  const normalizedRequestedTenantId = requestedTenantRaw && requestedTenantRaw.toLowerCase() === "all" ? null : requestedTenantRaw;
  const authTenantId = auth.tenantId ?? null;
  const container = await createRequestContainer();
  const isSuperAdmin = await resolveIsSuperAdmin({ auth, container });
  const em = container.resolve("em");
  const allowAllTenants = isSuperAdmin && !normalizedRequestedTenantId && query.view === "manage";
  let tenantId = allowAllTenants ? null : enforceTenantScope(authTenantId, normalizedRequestedTenantId, isSuperAdmin);
  const status = query.status ?? "all";
  const includeInactive = parseBooleanToken(query.includeInactive) === true || status !== "active";
  if (!allowAllTenants && !tenantId && !authTenantId && ids?.length) {
    let scopedIds = ids;
    if (!isSuperAdmin) {
      let allowedIds = null;
      try {
        const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
        allowedIds = Array.isArray(scope.allowedIds) ? scope.allowedIds : null;
      } catch {
      }
      const allowedSet = allowedIds ? new Set(allowedIds) : /* @__PURE__ */ new Set();
      scopedIds = ids.filter((id) => allowedSet.has(id));
    }
    if (scopedIds.length) {
      const scopedOrgs = await findWithDecryption(
        em,
        Organization,
        { id: { $in: scopedIds }, deletedAt: null },
        { populate: ["tenant"] },
        { tenantId: authTenantId, organizationId: null }
      );
      const tenantCandidates = /* @__PURE__ */ new Set();
      for (const org of scopedOrgs) {
        const orgTenantId = org.tenant?.id ? stringId(org.tenant.id) : "";
        if (orgTenantId) tenantCandidates.add(orgTenantId);
      }
      if (tenantCandidates.size === 1) {
        const candidateTenantId = Array.from(tenantCandidates)[0] ?? null;
        try {
          tenantId = await enforceTenantSelection({ auth, container }, candidateTenantId);
        } catch (error) {
          if (isCrudHttpError(error)) {
            return NextResponse.json(error.body, { status: error.status });
          }
          throw error;
        }
      } else if (tenantCandidates.size > 1) {
        return NextResponse.json({ items: [], error: "Tenant scope required" }, { status: 400 });
      }
    }
  }
  if (!allowAllTenants && !tenantId) {
    const candidateOrgIds = /* @__PURE__ */ new Set();
    const cookieOrgId = getSelectedOrganizationFromRequest(req);
    const effectiveCookieOrgId = cookieOrgId && !isAllOrganizationsSelection(cookieOrgId) ? cookieOrgId : null;
    if (effectiveCookieOrgId) candidateOrgIds.add(effectiveCookieOrgId);
    if (auth.orgId) candidateOrgIds.add(auth.orgId);
    try {
      const scope = await resolveOrganizationScopeForRequest({
        container,
        auth,
        request: req,
        selectedId: effectiveCookieOrgId ?? void 0
      });
      if (scope.selectedId) candidateOrgIds.add(scope.selectedId);
      if (Array.isArray(scope.filterIds) && scope.filterIds.length) {
        candidateOrgIds.add(scope.filterIds[0]);
      }
      if (Array.isArray(scope.allowedIds) && scope.allowedIds.length) {
        candidateOrgIds.add(scope.allowedIds[0]);
      }
    } catch {
    }
    for (const orgId of candidateOrgIds) {
      if (!orgId) continue;
      const org = await findOneWithDecryption(
        em,
        Organization,
        { id: orgId, deletedAt: null },
        { populate: ["tenant"] },
        { tenantId: authTenantId, organizationId: orgId }
      );
      if (org?.tenant && org.tenant.id) {
        tenantId = stringId(org.tenant.id);
        break;
      }
    }
  }
  if (!allowAllTenants && !tenantId) {
    return NextResponse.json({ items: [], error: "Tenant scope required" }, { status: 400 });
  }
  if (query.view === "options") {
    if (!tenantId) {
      return NextResponse.json({ items: [], error: "Tenant scope required" }, { status: 400 });
    }
    const where = { tenant: tenantId, deletedAt: null };
    if (status === "active") where.isActive = true;
    if (status === "inactive") where.isActive = false;
    if (status === "all" && !includeInactive) where.isActive = true;
    if (ids) where.id = { $in: ids };
    const orgs2 = await em.find(Organization, where, { orderBy: { name: "ASC" } });
    const items2 = orgs2.map((org) => ({
      id: stringId(org.id),
      name: org.name,
      logoUrl: org.logoUrl ?? null,
      parentId: org.parentId ?? null,
      tenantId,
      isActive: !!org.isActive,
      depth: org.depth ?? 0,
      treePath: org.treePath ?? stringId(org.id)
    }));
    await logCrudAccess({
      container,
      auth,
      request: req,
      items: items2,
      idField: "id",
      resourceKind: "directory.organization",
      organizationId: null,
      tenantId,
      query,
      accessType: ids && ids.length === 1 ? "read:item" : void 0
    });
    return NextResponse.json({ items: items2 });
  }
  if (query.view === "tree") {
    if (!tenantId) {
      return NextResponse.json({ items: [], error: "Tenant scope required" }, { status: 400 });
    }
    const orgListFilter2 = { tenant: tenantId, deletedAt: null };
    const orgs2 = await em.find(Organization, orgListFilter2, { orderBy: { name: "ASC" } });
    const hierarchy2 = computeHierarchyForOrganizations(orgs2, tenantId);
    const nodeMap = /* @__PURE__ */ new Map();
    const roots = [];
    for (const node of hierarchy2.ordered) {
      const treeNode = {
        id: node.id,
        name: node.name,
        parentId: node.parentId,
        tenantId: node.tenantId,
        depth: node.depth,
        ancestorIds: node.ancestorIds,
        childIds: node.childIds,
        descendantIds: node.descendantIds,
        isActive: node.isActive,
        treePath: node.treePath,
        pathLabel: node.pathLabel,
        children: []
      };
      nodeMap.set(node.id, { node, children: treeNode.children });
      if (node.parentId && nodeMap.has(node.parentId)) {
        const parentEntry = nodeMap.get(node.parentId);
        parentEntry.children.push(treeNode);
      } else {
        roots.push(treeNode);
      }
    }
    await logCrudAccess({
      container,
      auth,
      request: req,
      items: roots,
      idField: "id",
      resourceKind: "directory.organization",
      organizationId: null,
      tenantId,
      query
    });
    return NextResponse.json({ items: roots });
  }
  if (query.view !== "manage") {
    return NextResponse.json({ items: [] }, { status: 400 });
  }
  if (allowAllTenants) {
    const search2 = (query.search || "").trim().toLowerCase();
    const allOrgs = await findWithDecryption(
      em,
      Organization,
      { deletedAt: null },
      { orderBy: { name: "ASC" }, populate: ["tenant"] },
      { tenantId: null, organizationId: null }
    );
    const byTenant = /* @__PURE__ */ new Map();
    for (const org of allOrgs) {
      const tenantEntity = org.tenant;
      const tid = tenantEntity ? stringId(tenantEntity.id) : null;
      if (!tid) continue;
      if (!byTenant.has(tid)) byTenant.set(tid, []);
      byTenant.get(tid).push(org);
    }
    const slugByOrgId2 = /* @__PURE__ */ new Map();
    const updatedAtByOrgId2 = /* @__PURE__ */ new Map();
    const logoUrlByOrgId2 = /* @__PURE__ */ new Map();
    for (const org of allOrgs) {
      slugByOrgId2.set(String(org.id), org.slug ?? null);
      updatedAtByOrgId2.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null);
      logoUrlByOrgId2.set(String(org.id), org.logoUrl ?? null);
    }
    const tenantIds = Array.from(byTenant.keys());
    const tenants = tenantIds.length ? await em.find(Tenant, { id: { $in: tenantIds } }) : [];
    const tenantNameMap = tenants.reduce((acc, tenant) => {
      const tid = stringId(tenant.id);
      const name = typeof tenant.name === "string" && tenant.name.length > 0 ? tenant.name : tid;
      acc[tid] = name;
      return acc;
    }, {});
    const hierarchies = /* @__PURE__ */ new Map();
    const orderedNodes = [];
    for (const [tid, orgs2] of byTenant.entries()) {
      const hierarchy2 = computeHierarchyForOrganizations(orgs2, tid);
      hierarchies.set(tid, hierarchy2);
      for (const node of hierarchy2.ordered) {
        orderedNodes.push({ tenantId: tid, node });
      }
    }
    let rows2 = orderedNodes;
    if (query.status === "active") {
      rows2 = rows2.filter((entry) => entry.node.isActive);
    } else if (query.status === "inactive") {
      rows2 = rows2.filter((entry) => !entry.node.isActive);
    }
    if (search2) {
      rows2 = rows2.filter(({ node }) => {
        const pathLabel = (node.pathLabel || "").toLowerCase();
        return node.name.toLowerCase().includes(search2) || pathLabel.includes(search2);
      });
    }
    if (ids) {
      const idSet = new Set(ids);
      rows2 = rows2.filter(({ node }) => idSet.has(node.id));
    }
    rows2.sort((a, b) => {
      const nameA = tenantNameMap[a.tenantId] ?? a.tenantId;
      const nameB = tenantNameMap[b.tenantId] ?? b.tenantId;
      const tenantCompare = nameA.localeCompare(nameB);
      if (tenantCompare !== 0) return tenantCompare;
      return a.node.pathLabel.localeCompare(b.node.pathLabel);
    });
    const total2 = rows2.length;
    const pageSize2 = query.pageSize;
    const page2 = query.page;
    const start2 = (page2 - 1) * pageSize2;
    const paged2 = rows2.slice(start2, start2 + pageSize2);
    const recordIds2 = [];
    const tenantIdByRecord2 = {};
    const organizationIdByRecord2 = {};
    for (const entry of paged2) {
      const recordId = String(entry.node.id);
      recordIds2.push(recordId);
      tenantIdByRecord2[recordId] = entry.tenantId;
      organizationIdByRecord2[recordId] = recordId;
    }
    const tenantFallbacks = Array.from(new Set(recordIds2.map((id) => tenantIdByRecord2[id]).filter((value) => typeof value === "string" && value.length > 0)));
    const cfByOrg2 = recordIds2.length ? await loadCustomFieldValues({
      em,
      entityId: E.directory.organization,
      recordIds: recordIds2,
      tenantIdByRecord: tenantIdByRecord2,
      organizationIdByRecord: organizationIdByRecord2,
      tenantFallbacks
    }) : {};
    const items2 = paged2.map(({ tenantId: tid, node }) => {
      const hierarchy2 = hierarchies.get(tid);
      const parentName = node.parentId && hierarchy2 ? hierarchy2.map.get(node.parentId)?.name ?? null : null;
      const pathLabel = node.pathLabel || node.name;
      const recordId = String(node.id);
      return {
        id: node.id,
        name: node.name,
        slug: slugByOrgId2.get(recordId) ?? null,
        updatedAt: updatedAtByOrgId2.get(recordId) ?? null,
        logoUrl: logoUrlByOrgId2.get(recordId) ?? null,
        tenantId: tid,
        tenantName: tenantNameMap[tid] ?? tid,
        parentId: node.parentId,
        parentName,
        depth: node.depth,
        rootId: node.rootId,
        treePath: node.treePath,
        pathLabel,
        ancestorIds: node.ancestorIds,
        childIds: node.childIds,
        descendantIds: node.descendantIds,
        childrenCount: node.childIds.length,
        descendantsCount: node.descendantIds.length,
        isActive: node.isActive,
        ...cfByOrg2[recordId] ?? {}
      };
    });
    const totalPages2 = Math.max(1, Math.ceil(total2 / pageSize2));
    await logCrudAccess({
      container,
      auth,
      request: req,
      items: items2,
      idField: "id",
      resourceKind: "directory.organization",
      organizationId: null,
      tenantId: null,
      query,
      accessType: ids && ids.length === 1 ? "read:item" : void 0
    });
    return NextResponse.json({ items: items2, total: total2, page: page2, pageSize: pageSize2, totalPages: totalPages2, isSuperAdmin });
  }
  if (!tenantId) {
    return NextResponse.json({ items: [], error: "Tenant scope required" }, { status: 400 });
  }
  const orgListFilter = { tenant: tenantId, deletedAt: null };
  const orgs = await em.find(Organization, orgListFilter, { orderBy: { name: "ASC" } });
  const hierarchy = computeHierarchyForOrganizations(orgs, tenantId);
  const slugByOrgId = /* @__PURE__ */ new Map();
  const logoUrlByOrgId = /* @__PURE__ */ new Map();
  const updatedAtByOrgId = /* @__PURE__ */ new Map();
  for (const org of orgs) {
    slugByOrgId.set(String(org.id), org.slug ?? null);
    logoUrlByOrgId.set(String(org.id), org.logoUrl ?? null);
    updatedAtByOrgId.set(String(org.id), org.updatedAt instanceof Date ? org.updatedAt.toISOString() : null);
  }
  const search = (query.search || "").trim().toLowerCase();
  let rows = hierarchy.ordered;
  if (status === "active") {
    rows = rows.filter((node) => node.isActive);
  } else if (status === "inactive") {
    rows = rows.filter((node) => !node.isActive);
  }
  if (search) {
    rows = rows.filter((node) => {
      const pathLabel = (node.pathLabel || "").toLowerCase();
      return node.name.toLowerCase().includes(search) || pathLabel.includes(search);
    });
  }
  if (ids) {
    const idSet = new Set(ids);
    rows = rows.filter((node) => idSet.has(node.id));
  }
  const total = rows.length;
  const pageSize = query.pageSize;
  const page = query.page;
  const start = (page - 1) * pageSize;
  const paged = rows.slice(start, start + pageSize);
  const recordIds = [];
  const tenantIdByRecord = {};
  const organizationIdByRecord = {};
  for (const node of paged) {
    const recordId = String(node.id);
    recordIds.push(recordId);
    tenantIdByRecord[recordId] = node.tenantId ? String(node.tenantId) : null;
    organizationIdByRecord[recordId] = recordId;
  }
  const cfByOrg = recordIds.length ? await loadCustomFieldValues({
    em,
    entityId: E.directory.organization,
    recordIds,
    tenantIdByRecord,
    organizationIdByRecord,
    tenantFallbacks: tenantId ? [tenantId] : []
  }) : {};
  let tenantName = null;
  if (isSuperAdmin && tenantId) {
    const tenant = await em.findOne(Tenant, { id: tenantId });
    if (tenant) {
      const display = typeof tenant.name === "string" && tenant.name.length > 0 ? tenant.name : stringId(tenant.id);
      tenantName = display;
    }
  }
  const items = paged.map((node) => {
    const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null;
    const pathLabel = node.pathLabel || node.name;
    const recordId = String(node.id);
    return {
      id: node.id,
      name: node.name,
      slug: slugByOrgId.get(recordId) ?? null,
      logoUrl: logoUrlByOrgId.get(recordId) ?? null,
      updatedAt: updatedAtByOrgId.get(recordId) ?? null,
      tenantId: node.tenantId,
      tenantName,
      parentId: node.parentId,
      parentName,
      depth: node.depth,
      rootId: node.rootId,
      treePath: node.treePath,
      pathLabel,
      ancestorIds: node.ancestorIds,
      childIds: node.childIds,
      descendantIds: node.descendantIds,
      childrenCount: node.childIds.length,
      descendantsCount: node.descendantIds.length,
      isActive: node.isActive,
      ...cfByOrg[recordId] ?? {}
    };
  });
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
  await logCrudAccess({
    container,
    auth,
    request: req,
    items,
    idField: "id",
    resourceKind: "directory.organization",
    organizationId: null,
    tenantId,
    query,
    accessType: ids && ids.length === 1 ? "read:item" : void 0
  });
  return NextResponse.json({ items, total, page, pageSize, totalPages, isSuperAdmin });
}
const POST = crud.POST;
const PUT = crud.PUT;
const DELETE = crud.DELETE;
const organizationCreateResponseSchema = z.object({
  id: z.string().uuid()
});
const organizationDeleteRequestSchema = z.object({
  id: z.string().uuid()
});
const organizationsGetDoc = {
  summary: "List organizations",
  description: "Returns organizations using options, tree, or paginated manage view depending on the `view` parameter.",
  tags: [directoryTag],
  query: viewSchema,
  responses: [
    { status: 200, description: "Organization data for the requested view.", schema: organizationListResponseSchema }
  ],
  errors: [
    { status: 400, description: "Invalid query or tenant scope", schema: directoryErrorSchema },
    { status: 401, description: "Authentication required", schema: directoryErrorSchema }
  ]
};
const organizationsPostDoc = {
  summary: "Create organization",
  description: "Creates a new organization within a tenant and optionally assigns hierarchy relationships.",
  tags: [directoryTag],
  requestBody: {
    contentType: "application/json",
    schema: organizationCreateSchema,
    description: "Organization attributes and optional hierarchy configuration."
  },
  responses: [
    { status: 201, description: "Organization created.", schema: organizationCreateResponseSchema }
  ],
  errors: [
    { status: 400, description: "Validation failed", schema: directoryErrorSchema },
    { status: 401, description: "Authentication required", schema: directoryErrorSchema },
    { status: 403, description: "Missing directory.organizations.manage feature", schema: directoryErrorSchema }
  ]
};
const organizationsPutDoc = {
  summary: "Update organization",
  description: "Updates organization details and hierarchy assignments.",
  tags: [directoryTag],
  requestBody: {
    contentType: "application/json",
    schema: organizationUpdateSchema,
    description: "Organization identifier followed by fields to update."
  },
  responses: [
    { status: 200, description: "Organization updated.", schema: directoryOkSchema }
  ],
  errors: [
    { status: 400, description: "Validation failed", schema: directoryErrorSchema },
    { status: 401, description: "Authentication required", schema: directoryErrorSchema },
    { status: 403, description: "Missing directory.organizations.manage feature", schema: directoryErrorSchema }
  ]
};
const organizationsDeleteDoc = {
  summary: "Delete organization",
  description: "Soft deletes an organization identified by id.",
  tags: [directoryTag],
  requestBody: {
    contentType: "application/json",
    schema: organizationDeleteRequestSchema,
    description: "Identifier of the organization to delete."
  },
  responses: [
    { status: 200, description: "Organization deleted.", schema: directoryOkSchema }
  ],
  errors: [
    { status: 400, description: "Validation failed", schema: directoryErrorSchema },
    { status: 401, description: "Authentication required", schema: directoryErrorSchema },
    { status: 403, description: "Missing directory.organizations.manage feature", schema: directoryErrorSchema }
  ]
};
const openApi = {
  tag: directoryTag,
  summary: "Manage organizations",
  methods: {
    GET: organizationsGetDoc,
    POST: organizationsPostDoc,
    PUT: organizationsPutDoc,
    DELETE: organizationsDeleteDoc
  }
};
export {
  DELETE,
  GET,
  POST,
  PUT,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
