import { APIObservation } from "@/src/features/public-api/types/observations";
import {
  APIScoreSchemaV1,
  paginationMetaResponseZod,
  orderBy,
  publicApiPaginationZod,
  singleFilter,
  InvalidRequestError,
} from "@langfuse/shared";
import { stringDateTime, TraceBody } from "@langfuse/shared/src/server";
import { z } from "zod/v4";
import { useEventsTableSchema } from "../../query";

/**
 * Field groups for selective field fetching
 */
export const TRACE_FIELD_GROUPS = [
  "core",
  "io",
  "scores",
  "observations",
  "metrics",
] as const;

export type TraceFieldGroup = (typeof TRACE_FIELD_GROUPS)[number];

/**
 * Objects
 */

export const APITrace = z
  .object({
    id: z.string(),
    externalId: z.string().nullable(),
    timestamp: z.coerce.date(),
    name: z.string().nullable(),
    userId: z.string().nullable(),
    metadata: z.any(), // Prisma JSON
    release: z.string().nullable(),
    version: z.string().nullable(),
    projectId: z.string(),
    environment: z.string().default("default"),
    public: z.boolean(),
    bookmarked: z.boolean(),
    tags: z.array(z.string()),
    input: z.any(), // Prisma JSON
    output: z.any(), // Prisma JSON
    sessionId: z.string().nullable(),
    createdAt: z.coerce.date(),
    updatedAt: z.coerce.date(),
  })
  .strict();

const APIExtendedTrace = APITrace.extend({
  observations: z.array(z.string()).nullish(),
  scores: z.array(z.string()).nullish(),
  totalCost: z.number().nullish(),
  latency: z.number().nullish(),
  htmlPath: z.string(),
}).strict();

/**
 * Endpoints
 */

// GET /api/public/traces
export const GetTracesV1Query = z.object({
  ...publicApiPaginationZod,
  userId: z.string().nullish(),
  name: z.string().nullish(),
  tags: z.union([z.array(z.string()), z.string()]).nullish(),
  environment: z.union([z.array(z.string()), z.string()]).nullish(),
  sessionId: z.string().nullish(),
  version: z.string().nullish(),
  release: z.string().nullish(),
  fromTimestamp: stringDateTime,
  toTimestamp: stringDateTime,
  orderBy: z
    .string() // orderBy=timestamp.asc
    .nullish()
    .transform((v) => {
      if (!v) return null;
      const [column, order] = v.split(".");
      return { column, order: order?.toUpperCase() };
    })
    .pipe(orderBy.nullable()),
  fields: z
    .string()
    .nullish()
    .transform((v) => {
      if (!v) return null;
      return v
        .split(",")
        .map((f) => f.trim())
        .filter((f) => TRACE_FIELD_GROUPS.includes(f as TraceFieldGroup));
    })
    .pipe(z.array(z.enum(TRACE_FIELD_GROUPS)).nullable()),
  useEventsTable: useEventsTableSchema,
  filter: z
    .string()
    .optional()
    .transform((str) => {
      if (!str) return undefined;
      try {
        const parsed = JSON.parse(str);
        return parsed;
      } catch (e) {
        if (e instanceof InvalidRequestError) throw e;
        throw new InvalidRequestError("Invalid JSON in filter parameter");
      }
    })
    .pipe(z.array(singleFilter).optional()),
});
export const GetTracesV1Response = z
  .object({
    data: z.array(APIExtendedTrace),
    meta: paginationMetaResponseZod,
  })
  .strict();

// POST /api/public/traces
export const PostTracesV1Body = TraceBody;
export const PostTracesV1Response = z.object({ id: z.string() });

// GET /api/public/traces/{traceId}
export const GetTraceV1Query = z.object({
  traceId: z.string(),
});
export const GetTraceV1Response = APIExtendedTrace.extend({
  scores: z.array(APIScoreSchemaV1),
  observations: z.array(APIObservation),
}).strict();

// DELETE /api/public/traces/{traceId}
export const DeleteTraceV1Query = z.object({
  traceId: z.string(),
});
export const DeleteTraceV1Response = z
  .object({
    message: z.string(),
  })
  .strict();

// DELETE /api/public/traces
export const DeleteTracesV1Body = z
  .object({
    traceIds: z
      .array(z.string())
      .min(1, "At least 1 traceId is required.")
      .max(1000, "Cannot specify more than 1000 traces in a single request."),
  })
  .strict();
export const DeleteTracesV1Response = z
  .object({
    message: z.string(),
  })
  .strict();
