import type { operations } from "../__generated__/api/v1";
import { createClient } from "../client";
import {
  GET_SPANS_FILTERS,
  GET_SPANS_TRACE_IDS,
} from "../constants/serverRequirements";
import type { ClientFn } from "../types/core";
import type { ProjectIdentifier } from "../types/projects";
import { resolveProjectIdentifier } from "../types/projects";
import type { SpanKindFilter, SpanStatusCode } from "../types/spans";
import { ensureServerCapability } from "../utils/serverVersionUtils";

/**
 * Parameters to get spans from a project using auto-generated types
 */
export interface GetSpansParams extends ClientFn {
  /** The project to get spans from */
  project: ProjectIdentifier;
  /** Inclusive lower bound time. Must be a valid ISO 8601 string or Date object. */
  startTime?: Date | string | null;
  /** Exclusive upper bound time. Must be a valid ISO 8601 string or Date object. */
  endTime?: Date | string | null;
  /** Pagination cursor (Span Global ID) */
  cursor?: string | null;
  /** Maximum number of spans to return */
  limit?: number;
  /** Filter spans by one or more trace IDs */
  traceIds?: string[] | null;
  /** Filter by parent span ID. Use `null` or the string `"null"` to get root spans only. */
  parentId?: string | null;
  /** Filter by span name(s) */
  name?: string | string[] | null;
  /** Filter by span kind(s) (LLM, CHAIN, TOOL, RETRIEVER, etc.) */
  spanKind?: SpanKindFilter | SpanKindFilter[] | null;
  /** Filter by status code(s) (OK, ERROR, UNSET) */
  statusCode?: SpanStatusCode | SpanStatusCode[] | null;
}

export type GetSpansResponse = operations["getSpans"]["responses"]["200"];

export type GetSpansResult = {
  spans: GetSpansResponse["content"]["application/json"]["data"];
  nextCursor: GetSpansResponse["content"]["application/json"]["next_cursor"];
};

/**
 * Get spans from a project with filtering criteria.
 *
 * This method allows you to search for spans within a project using various filters
 * such as time range and supports cursor-based pagination.
 * The spans are returned in Phoenix's standard format with human-readable timestamps
 * and simplified attribute structures.
 *
 * @experimental this function is experimental and may change in the future
 *
 * @param params - The parameters to search for spans
 * @returns A paginated response containing spans and optional next cursor
 *
 * @requires Phoenix server >= 13.9.0 when filtering by `traceIds`
 *
 * @example
 * ```ts
 * // Get recent spans from a project
 * const result = await getSpans({
 *   client,
 *   project: { projectName: "my-project" },
 *   limit: 50
 * });
 *
 * // Get spans in a time range

 * const result = await getSpans({
 *   client,
 *   project: { projectName: "my-project" },
 *   startTime: new Date("2024-01-01"),
 *   endTime: new Date("2024-01-02"),
 *   limit: 100
 * });

 *
 * // Get all spans for specific traces (requires Phoenix server >= 13.9.0)
 * const result = await getSpans({
 *   client,
 *   project: { projectName: "my-project" },
 *   traceIds: ["trace-abc-123", "trace-def-456"],
 * });
 *
 * // Paginate through results
 * let cursor: string | undefined;
 * do {
 *   const result = await getSpans({
 *     client,
 *     project: { projectName: "my-project" },
 *     cursor,
 *     limit: 100
 *   });
 *
 *   // Process spans
 *   result.spans.forEach(span => {
 *     console.log(`Span: ${span.name}, Trace: ${span.context.trace_id}`);
 *   });
 *
 *   cursor = result.nextCursor || undefined;
 * } while (cursor);
 * ```
 */
export async function getSpans({
  client: _client,
  project,
  cursor,
  limit = 100,
  startTime,
  endTime,
  traceIds,
  parentId,
  name,
  spanKind,
  statusCode,
}: GetSpansParams): Promise<GetSpansResult> {
  const client = _client ?? createClient();
  if (traceIds) {
    await ensureServerCapability({ client, requirement: GET_SPANS_TRACE_IDS });
  }
  if (name != null || spanKind != null || statusCode != null) {
    await ensureServerCapability({ client, requirement: GET_SPANS_FILTERS });
  }
  const projectIdentifier = resolveProjectIdentifier(project);

  const params: NonNullable<operations["getSpans"]["parameters"]["query"]> = {
    limit,
  };

  if (cursor) {
    params.cursor = cursor;
  }

  if (startTime) {
    params.start_time =
      startTime instanceof Date ? startTime.toISOString() : startTime;
  }

  if (endTime) {
    params.end_time = endTime instanceof Date ? endTime.toISOString() : endTime;
  }

  if (traceIds) {
    params.trace_id = traceIds;
  }

  if (parentId !== undefined) {
    params.parent_id = parentId === null ? "null" : parentId;
  }

  if (name) {
    params.name = Array.isArray(name) ? name : [name];
  }

  if (spanKind) {
    params.span_kind = Array.isArray(spanKind) ? spanKind : [spanKind];
  }

  if (statusCode) {
    params.status_code = Array.isArray(statusCode) ? statusCode : [statusCode];
  }

  const { data, error } = await client.GET(
    "/v1/projects/{project_identifier}/spans",
    {
      params: {
        path: {
          project_identifier: projectIdentifier,
        },
        query: params,
      },
    }
  );

  if (error) throw error;
  return {
    spans: data?.data ?? [],
    nextCursor: data?.next_cursor ?? null,
  };
}
