import { NextResponse } from "next/server";
import { z } from "zod";
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
import { E } from "../../../../generated/entities.ids.generated.js";
import { CatalogProductCategory } from "../../data/entities.js";
import { categoryCreateSchema, categoryUpdateSchema } from "../../data/validators.js";
import { parseScopedCommandInput, resolveCrudRecordId } from "../utils.js";
import { computeHierarchyForCategories } from "../../lib/categoryHierarchy.js";
import {
  createCatalogCrudOpenApi,
  createPagedListResponseSchema,
  defaultOkResponseSchema
} from "../openapi.js";
const routeMetadata = {
  GET: { requireAuth: true, requireFeatures: ["catalog.categories.view"] },
  POST: { requireAuth: true, requireFeatures: ["catalog.categories.manage"] },
  PUT: { requireAuth: true, requireFeatures: ["catalog.categories.manage"] },
  DELETE: { requireAuth: true, requireFeatures: ["catalog.categories.manage"] }
};
const metadata = routeMetadata;
const rawBodySchema = z.object({}).passthrough();
const viewSchema = z.object({
  view: z.enum(["manage", "tree"]).default("manage"),
  page: z.coerce.number().min(1).default(1),
  pageSize: z.coerce.number().min(1).max(200).default(50),
  search: z.string().optional(),
  status: z.enum(["all", "active", "inactive"]).optional(),
  ids: z.string().optional()
}).passthrough();
const crud = makeCrudRoute({
  metadata: routeMetadata,
  orm: {
    entity: CatalogProductCategory,
    idField: "id",
    orgField: "organizationId",
    tenantField: "tenantId",
    softDeleteField: "deletedAt"
  },
  actions: {
    create: {
      commandId: "catalog.categories.create",
      schema: rawBodySchema,
      mapInput: async ({ raw, ctx }) => {
        const { translate } = await resolveTranslations();
        return parseScopedCommandInput(categoryCreateSchema, raw ?? {}, ctx, translate);
      },
      response: ({ result }) => ({ id: result?.categoryId ?? null }),
      status: 201
    },
    update: {
      commandId: "catalog.categories.update",
      schema: rawBodySchema,
      mapInput: async ({ raw, ctx }) => {
        const { translate } = await resolveTranslations();
        return parseScopedCommandInput(categoryUpdateSchema, raw ?? {}, ctx, translate);
      },
      response: () => ({ ok: true })
    },
    delete: {
      commandId: "catalog.categories.delete",
      schema: rawBodySchema,
      mapInput: async ({ parsed, ctx }) => {
        const { translate } = await resolveTranslations();
        const id = resolveCrudRecordId(parsed, ctx, translate);
        if (!id) throw new CrudHttpError(400, { error: translate("catalog.errors.id_required", "Record identifier is required.") });
        return { id };
      },
      response: () => ({ ok: true })
    }
  }
});
function sanitizeSearch(term) {
  if (!term) return "";
  return term.trim().toLowerCase();
}
function parseIds(raw) {
  if (!raw) return null;
  const parts = raw.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
  return parts.length ? Array.from(new Set(parts)) : null;
}
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({
    view: url.searchParams.get("view") ?? void 0,
    page: url.searchParams.get("page") ?? void 0,
    pageSize: url.searchParams.get("pageSize") ?? void 0,
    search: url.searchParams.get("search") ?? void 0,
    status: url.searchParams.get("status") ?? void 0,
    ids: url.searchParams.get("ids") ?? void 0
  });
  if (!parsed.success) {
    return NextResponse.json({ items: [], error: "Invalid query" }, { status: 400 });
  }
  const query = parsed.data;
  const container = await createRequestContainer();
  const em = container.resolve("em");
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
  const { translate } = await resolveTranslations();
  const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
  if (!tenantId) {
    return NextResponse.json(
      { items: [], error: translate("catalog.errors.tenant_required", "Tenant context is required.") },
      { status: 400 }
    );
  }
  const allowed = scope?.filterIds ?? scope?.allowedIds ?? (auth.orgId ? [auth.orgId] : null);
  const preferredOrg = scope?.selectedId ?? auth.orgId ?? null;
  const organizationId = preferredOrg ?? (Array.isArray(allowed) && allowed.length ? allowed[0] : null);
  if (!organizationId || Array.isArray(allowed) && allowed.length && !allowed.includes(organizationId)) {
    return NextResponse.json(
      { items: [], error: translate("catalog.errors.organization_required", "Organization context is required.") },
      { status: 400 }
    );
  }
  const categories = await em.find(
    CatalogProductCategory,
    { organizationId, tenantId, deletedAt: null },
    { orderBy: { name: "ASC" } }
  );
  const categoryMap = new Map(categories.map((cat) => [String(cat.id), cat]));
  const hierarchy = computeHierarchyForCategories(categories);
  if (query.view === "tree") {
    const nodes = /* @__PURE__ */ new Map();
    const roots = [];
    for (const entry of hierarchy.ordered) {
      const node = {
        id: entry.id,
        name: entry.name,
        parentId: entry.parentId,
        depth: entry.depth,
        pathLabel: entry.pathLabel,
        ancestorIds: entry.ancestorIds,
        childIds: entry.childIds,
        descendantIds: entry.descendantIds,
        isActive: entry.isActive,
        children: []
      };
      nodes.set(entry.id, node);
      if (entry.parentId && nodes.has(entry.parentId)) {
        nodes.get(entry.parentId).children.push(node);
      } else {
        roots.push(node);
      }
    }
    return NextResponse.json({ items: roots });
  }
  const status = query.status ?? "all";
  const search = sanitizeSearch(query.search ?? null);
  const ids = parseIds(query.ids);
  const idSet = ids ? new Set(ids) : null;
  let rows = hierarchy.ordered;
  if (status === "active") rows = rows.filter((node) => node.isActive);
  if (status === "inactive") rows = rows.filter((node) => !node.isActive);
  if (search) {
    rows = rows.filter((node) => {
      const label = node.pathLabel.toLowerCase();
      return node.name.toLowerCase().includes(search) || label.includes(search);
    });
  }
  if (idSet && idSet.size) {
    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 = paged.map((node) => node.id);
  const tenantIdByRecord = {};
  const organizationIdByRecord = {};
  for (const id of recordIds) {
    tenantIdByRecord[id] = tenantId;
    organizationIdByRecord[id] = organizationId;
  }
  const cfValues = recordIds.length ? await loadCustomFieldValues({
    em,
    entityId: E.catalog.catalog_product_category,
    recordIds,
    tenantIdByRecord,
    organizationIdByRecord,
    tenantFallbacks: tenantId ? [tenantId] : []
  }) : {};
  const items = paged.map((node) => {
    const category = categoryMap.get(node.id);
    const parentName = node.parentId ? hierarchy.map.get(node.parentId)?.name ?? null : null;
    const recordId = node.id;
    return {
      id: recordId,
      name: node.name,
      slug: category?.slug ?? null,
      description: category?.description ?? null,
      parentId: node.parentId,
      parentName,
      depth: node.depth,
      treePath: node.treePath,
      pathLabel: node.pathLabel,
      childCount: node.childIds.length,
      descendantCount: node.descendantIds.length,
      isActive: node.isActive,
      updatedAt: category?.updatedAt ? new Date(category.updatedAt).toISOString() : null,
      organizationId,
      tenantId,
      ...cfValues[recordId] ?? {}
    };
  });
  const totalPages = Math.max(1, Math.ceil(total / pageSize));
  return NextResponse.json({
    items,
    total,
    page,
    pageSize,
    totalPages,
    organizationId,
    tenantId
  });
}
const POST = crud.POST;
const PUT = crud.PUT;
const DELETE = crud.DELETE;
const categoryListItemSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  slug: z.string().nullable().optional(),
  description: z.string().nullable().optional(),
  parentId: z.string().uuid().nullable().optional(),
  parentName: z.string().nullable().optional(),
  depth: z.number(),
  treePath: z.string(),
  pathLabel: z.string(),
  childCount: z.number(),
  descendantCount: z.number(),
  isActive: z.boolean(),
  updatedAt: z.string().nullable().optional(),
  organizationId: z.string().uuid(),
  tenantId: z.string().uuid()
});
const openApi = createCatalogCrudOpenApi({
  resourceName: "Category",
  pluralName: "Categories",
  querySchema: viewSchema,
  listResponseSchema: createPagedListResponseSchema(categoryListItemSchema),
  create: {
    schema: categoryCreateSchema,
    description: "Creates a new product category."
  },
  update: {
    schema: categoryUpdateSchema,
    responseSchema: defaultOkResponseSchema,
    description: "Updates an existing category by id."
  },
  del: {
    schema: z.object({ id: z.string().uuid() }),
    responseSchema: defaultOkResponseSchema,
    description: "Deletes a category by id."
  }
});
export {
  DELETE,
  GET,
  POST,
  PUT,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
