import { NextResponse } from "next/server";
import { z } from "zod";
import { CustomerLabel, CustomerLabelAssignment } from "../../data/entities.js";
import { labelCreateCommandSchema, labelCreateSchema } from "../../data/validators.js";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
import { CrudHttpError, isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { decryptEntitiesWithFallbackScope } from "@open-mercato/shared/lib/encryption/subscriber";
import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
import { resolveEncryptedSortMaxRows, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
import { SortDir } from "@open-mercato/shared/lib/query/types";
import { resolveLabelActorUserId } from "./auth.js";
import {
  runCrudMutationGuardAfterSuccess,
  validateCrudMutationGuard
} from "@open-mercato/shared/lib/crud/mutation-guard";
import {
  createMissingCustomerLabelTablesError,
  isMissingCustomerLabelTable
} from "./table-errors.js";
import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
const metadata = {
  GET: { requireAuth: true, requireFeatures: ["customers.people.view"] },
  POST: { requireAuth: true, requireFeatures: ["customers.people.manage"] }
};
const DECRYPT_CONCURRENCY = 8;
const querySchema = z.object({
  entityId: z.string().uuid().optional(),
  organizationId: z.string().uuid().optional(),
  ids: z.string().optional(),
  page: z.coerce.number().min(1).default(1),
  pageSize: z.coerce.number().min(1).max(100).default(50),
  search: z.string().optional()
});
const createLabelRequestSchema = labelCreateSchema.extend({
  organizationId: z.string().uuid().optional()
});
async function GET(req) {
  const { translate } = await resolveTranslations();
  try {
    const container = await createRequestContainer();
    const auth = await getAuthFromRequest(req);
    const actorUserId = resolveLabelActorUserId(auth);
    if (!auth || !auth.tenantId || !actorUserId) {
      return NextResponse.json({ error: translate("customers.errors.unauthorized", "Unauthorized") }, { status: 401 });
    }
    const em = container.resolve("em");
    const url = new URL(req.url);
    const query = querySchema.parse({
      entityId: url.searchParams.get("entityId") ?? void 0,
      organizationId: url.searchParams.get("organizationId") ?? void 0,
      ids: url.searchParams.get("ids") ?? void 0,
      page: url.searchParams.get("page") ?? void 0,
      pageSize: url.searchParams.get("pageSize") ?? void 0,
      search: url.searchParams.get("search") ?? void 0
    });
    const scope = await resolveOrganizationScopeForRequest({
      container,
      auth,
      request: req,
      selectedId: query.organizationId ?? void 0
    });
    const organizationId = scope?.selectedId ?? auth.orgId ?? null;
    if (!organizationId) {
      throw new CrudHttpError(400, { error: translate("customers.errors.organization_required", "Organization context is required") });
    }
    const cap = resolveEncryptedSortMaxRows();
    const candidateLabels = await em.find(CustomerLabel, {
      tenantId: auth.tenantId,
      organizationId,
      userId: actorUserId
    }, cap !== null ? { limit: cap, orderBy: { id: "asc" } } : {});
    if (cap !== null && candidateLabels.length >= cap) {
      logger.warn("Encrypted sort candidate scan hit OM_ENCRYPTED_SORT_MAX_ROWS cap; results may be incomplete", {
        component: "labels.GET",
        cap,
        tenantId: auth.tenantId,
        organizationId
      });
    }
    await mapWithConcurrency(
      candidateLabels,
      DECRYPT_CONCURRENCY,
      (label) => decryptEntitiesWithFallbackScope(label, { em, tenantId: auth.tenantId, organizationId })
    );
    const labels = sortRowsInMemory(
      candidateLabels,
      [{ field: "label", dir: SortDir.Asc }]
    );
    let assignedLabelIds = [];
    if (query.entityId) {
      const assignments = await findWithDecryption(em, CustomerLabelAssignment, {
        tenantId: auth.tenantId,
        organizationId,
        userId: actorUserId,
        entity: query.entityId
      }, {}, { tenantId: auth.tenantId, organizationId });
      assignedLabelIds = assignments.map((a) => {
        try {
          return a.label.id;
        } catch {
          return "";
        }
      });
      if (assignedLabelIds.some((id) => !id)) {
        const loaded = await findWithDecryption(em, CustomerLabelAssignment, {
          tenantId: auth.tenantId,
          organizationId,
          userId: actorUserId,
          entity: query.entityId
        }, { populate: ["label"] }, { tenantId: auth.tenantId, organizationId });
        assignedLabelIds = loaded.map((a) => a.label.id);
      }
    }
    const requestedIds = query.ids ? new Set(
      query.ids.split(",").map((value) => value.trim()).filter(Boolean)
    ) : null;
    const scopedLabels = requestedIds ? labels.filter((label) => requestedIds.has(label.id)) : labels;
    const filteredLabels = query.search?.trim().length ? scopedLabels.filter((label) => label.label.toLowerCase().includes(query.search.trim().toLowerCase())) : scopedLabels;
    const total = filteredLabels.length;
    const totalPages = Math.max(1, Math.ceil(total / query.pageSize));
    const page = Math.min(query.page, totalPages);
    const start = (page - 1) * query.pageSize;
    const items = filteredLabels.slice(start, start + query.pageSize);
    return NextResponse.json({
      items: items.map((l) => ({
        id: l.id,
        slug: l.slug,
        label: l.label
      })),
      assignedIds: assignedLabelIds.filter(Boolean),
      total,
      page,
      pageSize: query.pageSize,
      totalPages
    });
  } catch (err) {
    if (isMissingCustomerLabelTable(err)) {
      return NextResponse.json({ items: [], assignedIds: [] });
    }
    if (isCrudHttpError(err)) {
      return NextResponse.json(err.body, { status: err.status });
    }
    logger.error("customers/labels.GET", { err });
    return NextResponse.json({ error: translate("customers.errors.labels_load_failed", "Failed to load labels") }, { status: 500 });
  }
}
function slugifyLabel(value) {
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
}
async function POST(req) {
  try {
    const { translate } = await resolveTranslations();
    const container = await createRequestContainer();
    const auth = await getAuthFromRequest(req);
    const actorUserId = resolveLabelActorUserId(auth);
    if (!auth || !auth.tenantId || !actorUserId) {
      return NextResponse.json({ error: translate("customers.errors.unauthorized", "Unauthorized") }, { status: 401 });
    }
    const body = createLabelRequestSchema.parse(await readJsonSafe(req, {}));
    const scope = await resolveOrganizationScopeForRequest({
      container,
      auth,
      request: req,
      selectedId: body.organizationId ?? void 0
    });
    const organizationId = scope?.selectedId ?? auth.orgId ?? null;
    if (!organizationId) {
      throw new CrudHttpError(400, { error: translate("customers.errors.organization_required", "Organization context is required") });
    }
    const slug = body.slug || slugifyLabel(body.label);
    const guardResult = await validateCrudMutationGuard(container, {
      tenantId: auth.tenantId,
      organizationId,
      userId: actorUserId,
      resourceKind: "customers.label",
      resourceId: organizationId,
      operation: "custom",
      requestMethod: req.method,
      requestHeaders: req.headers,
      mutationPayload: body
    });
    if (guardResult && !guardResult.ok) {
      return NextResponse.json(guardResult.body, { status: guardResult.status });
    }
    const commandInput = labelCreateCommandSchema.parse({
      tenantId: auth.tenantId,
      organizationId,
      userId: actorUserId,
      slug,
      label: body.label
    });
    const commandBus = container.resolve("commandBus");
    const { result, logEntry } = await commandBus.execute(
      "customers.labels.create",
      {
        input: commandInput,
        ctx: {
          container,
          auth,
          organizationScope: scope,
          selectedOrganizationId: organizationId,
          organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
          request: req
        }
      }
    );
    if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
      await runCrudMutationGuardAfterSuccess(container, {
        tenantId: auth.tenantId,
        organizationId,
        userId: actorUserId,
        resourceKind: "customers.label",
        resourceId: organizationId,
        operation: "custom",
        requestMethod: req.method,
        requestHeaders: req.headers,
        metadata: guardResult.metadata ?? null
      });
    }
    const response = NextResponse.json({
      id: result.labelId,
      slug: result.slug,
      label: result.label
    }, { status: 201 });
    if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {
      response.headers.set(
        "x-om-operation",
        serializeOperationMetadata({
          id: logEntry.id,
          undoToken: logEntry.undoToken,
          commandId: logEntry.commandId,
          actionLabel: logEntry.actionLabel ?? null,
          resourceKind: logEntry.resourceKind ?? "customers.label",
          resourceId: logEntry.resourceId ?? result.labelId,
          executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
        })
      );
    }
    return response;
  } catch (err) {
    if (isMissingCustomerLabelTable(err)) {
      const migrationError = await createMissingCustomerLabelTablesError();
      return NextResponse.json(migrationError.body, { status: migrationError.status });
    }
    if (isCrudHttpError(err)) {
      return NextResponse.json(err.body, { status: err.status });
    }
    logger.error("customers/labels.POST", { err });
    return NextResponse.json({ error: "Failed to create label" }, { status: 500 });
  }
}
const labelSchema = z.object({
  id: z.string().uuid(),
  slug: z.string(),
  label: z.string()
});
const labelsListSchema = z.object({
  items: z.array(labelSchema),
  assignedIds: z.array(z.string().uuid()),
  total: z.number().int().nonnegative(),
  page: z.number().int().min(1),
  pageSize: z.number().int().min(1),
  totalPages: z.number().int().min(1)
});
const errorSchema = z.object({ error: z.string() });
const openApi = {
  tag: "Customers",
  summary: "Customer labels (user- and organization-scoped)",
  methods: {
    GET: {
      summary: "List labels",
      description: "Returns labels for the current user within the selected organization. Optionally includes assignment status for a specific entity.",
      responses: [
        { status: 200, description: "Labels list", schema: labelsListSchema }
      ],
      errors: [
        { status: 401, description: "Unauthorized", schema: errorSchema }
      ]
    },
    POST: {
      summary: "Create label",
      description: "Creates a new label scoped to the current user and selected organization.",
      requestBody: { contentType: "application/json", schema: createLabelRequestSchema },
      responses: [
        { status: 201, description: "Label created", schema: labelSchema }
      ],
      errors: [
        { status: 409, description: "Duplicate slug", schema: errorSchema }
      ]
    }
  }
};
export {
  GET,
  POST,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
