import { z } from "zod";
import type { ZodType } from "zod";
import type { ToolBinder } from "../core/binder";

/**
 * Collection Bindings
 *
 * This module provides standardized tool bindings for Collections, representing
 * SQL table-like structures with CRUD + Search operations compatible with TanStack DB.
 *
 * Key Features:
 * - Generic collection bindings that work with any entity type
 * - Standardized tool naming: `COLLECTION_{COLLECTION}_*`
 * - Compatible with TanStack DB query-collection
 * - Full TypeScript support with proper type constraints
 * - Support for filtering, sorting, and pagination
 * - Simple id and title fields for human-readable identification
 */

/**
 * Base schema for collection entities
 * All collection entities must have an id, title, and audit trail fields
 */
export const BaseCollectionEntitySchema = z.object({
  id: z.string().describe("Unique identifier for the entity"),
  title: z.string().describe("Human-readable title for the entity"),
  description: z.string().nullish().describe("Description of the entity"),
  created_at: z.string().datetime(),
  updated_at: z.string().datetime(),
  created_by: z.string().optional(),
  updated_by: z.string().optional(),
});

/**
 * Type helper for BaseCollectionEntitySchema
 */
export type BaseCollectionEntitySchemaType = typeof BaseCollectionEntitySchema;

/**
 * Comparison expression schema for filtering
 */
const ComparisonExpressionSchema = z.object({
  field: z.array(z.string()),
  operator: z.enum(["eq", "gt", "gte", "lt", "lte", "in", "like", "contains"]),
  value: z.unknown(),
});

/**
 * Where expression type for filtering
 * Recursive to allow nested logical operators
 */
export type WhereExpression =
  | z.infer<typeof ComparisonExpressionSchema>
  | {
      operator: "and" | "or" | "not";
      conditions: WhereExpression[];
    };

/**
 * Where expression schema for filtering
 * Supports TanStack DB predicate push-down patterns
 * Recursive to allow nested logical operators
 */
export const WhereExpressionSchema: z.ZodType<WhereExpression> = z.lazy(() =>
  z.union([
    ComparisonExpressionSchema,
    z.object({
      operator: z.enum(["and", "or", "not"]),
      conditions: z.array(WhereExpressionSchema),
    }),
  ]),
);

/**
 * Order by expression for sorting
 */
export const OrderByExpressionSchema = z.object({
  field: z.array(z.string()),
  direction: z.enum(["asc", "desc"]),
  nulls: z.enum(["first", "last"]).optional(),
});

/**
 * List/Query input schema for collections
 * Compatible with TanStack DB LoadSubsetOptions
 */
export const CollectionListInputSchema = z.object({
  where: WhereExpressionSchema.optional().describe("Filter expression"),
  orderBy: z
    .array(OrderByExpressionSchema)
    .optional()
    .describe("Sort expressions"),
  limit: z
    .number()
    .int()
    .min(1)
    .max(1000)
    .optional()
    .describe("Maximum number of items to return"),
  offset: z
    .number()
    .int()
    .min(0)
    .optional()
    .describe("Number of items to skip"),
});

/**
 * Factory function to create list output schema for a specific collection type
 */
export function createCollectionListOutputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    items: z.array(entitySchema).describe("Array of collection items"),
    totalCount: z
      .number()
      .int()
      .min(0)
      .optional()
      .describe("Total number of matching items (if available)"),
    hasMore: z
      .boolean()
      .optional()
      .describe("Whether there are more items available"),
  });
}

/**
 * Get by ID input schema
 */
export const CollectionGetInputSchema = z.object({
  id: z.string().describe("ID of the entity to retrieve"),
});

/**
 * Factory function to create get output schema
 */
export function createCollectionGetOutputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    item: entitySchema
      .nullable()
      .describe("The retrieved item, or null if not found"),
  });
}

/**
 * Factory function to create insert input schema
 */
export function createCollectionInsertInputSchema<
  T extends z.ZodObject<z.ZodRawShape>,
>(entitySchema: T) {
  // Remove id field since it may be auto-generated by the server
  return z.object({
    data: entitySchema
      .partial()
      .describe("Data for the new entity (id may be auto-generated)"),
  });
}

/**
 * Factory function to create insert output schema
 */
export function createCollectionInsertOutputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    item: entitySchema.describe("The created entity with generated id"),
  });
}

/**
 * Factory function to create update input schema
 */
export function createCollectionUpdateInputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    id: z.string().describe("ID of the entity to update"),
    data: (entitySchema as unknown as z.ZodObject<z.ZodRawShape>)
      .partial()
      .describe("Partial entity data to update"),
  });
}

/**
 * Factory function to create update output schema
 */
export function createCollectionUpdateOutputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    item: entitySchema.describe("The updated entity"),
  });
}

/**
 * Delete input schema
 */
export const CollectionDeleteInputSchema = z.object({
  id: z.string().describe("ID of the entity to delete"),
});

/**
 * Factory function to create delete output schema
 */
export function createCollectionDeleteOutputSchema<T extends z.ZodTypeAny>(
  entitySchema: T,
) {
  return z.object({
    item: entitySchema.describe("The deleted entity"),
  });
}

/**
 * Options for creating collection bindings
 */
export interface CollectionBindingOptions {
  /**
   * If true, only LIST and GET operations will be included (read-only collection)
   * @default false
   */
  readOnly?: boolean;
}

/**
 * Creates generic collection bindings for a specific entity type
 *
 * This function generates standardized tool bindings that work with any collection/table
 * by accepting a custom entity schema and collection name. The bindings provide:
 * - COLLECTION_{NAME}_LIST - Query/search entities with filtering and sorting (required)
 * - COLLECTION_{NAME}_GET - Get a single entity by ID (required)
 * - COLLECTION_{NAME}_CREATE - Create a new entity (optional, excluded if readOnly=true)
 * - COLLECTION_{NAME}_UPDATE - Update an existing entity (optional, excluded if readOnly=true)
 * - COLLECTION_{NAME}_DELETE - Delete an entity (optional, excluded if readOnly=true)
 *
 * @param collectionName - The name of the collection/table (e.g., "users", "products", "orders")
 * @param entitySchema - The Zod schema for the entity type (must extend BaseCollectionEntitySchema)
 * @param options - Optional configuration for the collection bindings
 * @returns Array of tool bindings for Collection CRUD + Query operations
 *
 * @example
 * ```typescript
 * const UserSchema = z.object({
 *   id: z.string(),
 *   title: z.string(),
 *   created_at: z.string().datetime(),
 *   updated_at: z.string().datetime(),
 *   created_by: z.string().optional(),
 *   updated_by: z.string().optional(),
 *   email: z.string().email(),
 * });
 *
 * // Full CRUD collection
 * const USER_COLLECTION_BINDING = createCollectionBindings("users", UserSchema);
 *
 * // Read-only collection (only LIST and GET)
 * const READONLY_COLLECTION_BINDING = createCollectionBindings("products", ProductSchema, { readOnly: true });
 * ```
 */
export function createCollectionBindings<
  TEntitySchema extends z.ZodObject<z.ZodRawShape>,
  TName extends string,
>(
  collectionName: TName,
  entitySchema: TEntitySchema,
  options?: CollectionBindingOptions,
) {
  const upperName = collectionName.toUpperCase() as Uppercase<TName>;
  const readOnly = options?.readOnly ?? false;

  const bindings: ToolBinder[] = [
    {
      name: `COLLECTION_${upperName}_LIST` as const,
      inputSchema: CollectionListInputSchema,
      outputSchema: createCollectionListOutputSchema(entitySchema),
    },
    {
      name: `COLLECTION_${upperName}_GET` as const,
      inputSchema: CollectionGetInputSchema,
      outputSchema: createCollectionGetOutputSchema(entitySchema),
    },
  ];

  // Only include mutation operations if not read-only
  if (!readOnly) {
    bindings.push(
      {
        name: `COLLECTION_${upperName}_CREATE` as const,
        inputSchema: createCollectionInsertInputSchema(entitySchema),
        outputSchema: createCollectionInsertOutputSchema(entitySchema),
        opt: true,
      },
      {
        name: `COLLECTION_${upperName}_UPDATE` as const,
        inputSchema: createCollectionUpdateInputSchema(entitySchema),
        outputSchema: createCollectionUpdateOutputSchema(entitySchema),
        opt: true,
      },
      {
        name: `COLLECTION_${upperName}_DELETE` as const,
        inputSchema: CollectionDeleteInputSchema,
        outputSchema: createCollectionDeleteOutputSchema(entitySchema),
        opt: true,
      },
    );
  }

  return bindings as unknown as CollectionBinding<
    TEntitySchema,
    Uppercase<TName>
  >;
}

// Helper type for tool definition
type ToolBinding<
  Name extends string,
  Input,
  Output,
  Opt extends boolean = false,
> = {
  name: Name;
  inputSchema: ZodType<Input>;
  outputSchema: ZodType<Output>;
} & (Opt extends true ? { opt: true } : unknown);

/**
 * Type helper to extract the collection binding type
 */
export type CollectionBinding<
  TEntitySchema,
  TUpperName extends Uppercase<string> = Uppercase<string>,
  TEntity = TEntitySchema extends z.ZodObject<z.ZodRawShape>
    ? z.infer<TEntitySchema>
    : TEntitySchema,
> = [
  ToolBinding<
    `COLLECTION_${TUpperName}_LIST`,
    CollectionListInput,
    CollectionListOutput<TEntity>
  >,
  ToolBinding<
    `COLLECTION_${TUpperName}_GET`,
    CollectionGetInput,
    CollectionGetOutput<TEntity>
  >,
  ToolBinding<
    `COLLECTION_${TUpperName}_CREATE`,
    CollectionInsertInput<TEntity>,
    CollectionInsertOutput<TEntity>,
    true
  >,
  ToolBinding<
    `COLLECTION_${TUpperName}_UPDATE`,
    CollectionUpdateInput<TEntity>,
    CollectionUpdateOutput<TEntity>,
    true
  >,
  ToolBinding<
    `COLLECTION_${TUpperName}_DELETE`,
    CollectionDeleteInput,
    CollectionDeleteOutput<TEntity>,
    true
  >,
];

/**
 * Type helper to extract tool names from a collection binding
 */
export type CollectionTools<
  TEntitySchema extends z.ZodObject<z.ZodRawShape>,
  TUpperName extends Uppercase<string> = Uppercase<string>,
> = CollectionBinding<TEntitySchema, TUpperName>[number]["name"];

// Export types for TypeScript usage
export type CollectionListInput = z.infer<typeof CollectionListInputSchema>;
export type CollectionGetInput = z.infer<typeof CollectionGetInputSchema>;
export type CollectionDeleteInput = z.infer<typeof CollectionDeleteInputSchema>;
export type OrderByExpression = z.infer<typeof OrderByExpressionSchema>;

/**
 * Type helper for insert input with generic item type
 */
export type CollectionInsertInput<T> = {
  data: Partial<T>;
};

/**
 * Type helper for update input with generic item type
 */
export type CollectionUpdateInput<T> = {
  id: string;
  data: Partial<T>;
};

/**
 * Type helper for list output with generic item type
 */
export type CollectionListOutput<T> = {
  items: T[];
  totalCount?: number;
  hasMore?: boolean;
};

/**
 * Type helper for get output with generic item type
 */
export type CollectionGetOutput<T> = {
  item: T | null;
};

/**
 * Type helper for insert output with generic item type
 */
export type CollectionInsertOutput<T> = {
  item: T;
};

/**
 * Type helper for update output with generic item type
 */
export type CollectionUpdateOutput<T> = {
  item: T;
};

/**
 * Type helper for delete output with generic item type
 */
export type CollectionDeleteOutput<T> = {
  item: T;
};

/**
 * Base collection entity type - inferred from BaseCollectionEntitySchema
 */
export type BaseCollectionEntity = z.infer<typeof BaseCollectionEntitySchema>;
