import type { JSONSchema7 } from 'json-schema'

/**
 * Configuration options for schema filtering during import.
 */
export interface SchemaFilteringOptions {
  /**
   * Whether to exclude schemas that are empty objects with no properties.
   * Default: false
   */
  excludeEmptyObjects?: boolean

  /**
   * Whether to exclude schemas that only serve as conceptual markers.
   * These are typically schemas with only type: "object" and description
   * but no properties, constraints, or meaningful structure.
   * Default: false
   */
  excludeConceptualMarkers?: boolean

  /**
   * Custom predicate function to determine if a schema should be excluded.
   * Return true to exclude the schema from import.
   */
  customExclusionFilter?: (schema: JSONSchema7, schemaId?: string) => boolean

  /**
   * List of schema IDs to explicitly exclude.
   */
  excludeSchemaIds?: string[]

  /**
   * Patterns to match against schema titles or IDs for exclusion.
   */
  excludePatterns?: RegExp[]
}

/**
 * Strategy class for determining which schemas should be filtered out during import.
 */
export class SchemaFilteringStrategy {
  constructor(private options: SchemaFilteringOptions = {}) {}

  /**
   * Determines whether a schema should be excluded from import.
   */
  shouldExcludeSchema(schema: JSONSchema7, schemaId?: string): boolean {
    // Check explicit exclusion list
    if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
      return true
    }

    // Check pattern exclusions
    if (schemaId && this.options.excludePatterns) {
      for (const pattern of this.options.excludePatterns) {
        if (pattern.test(schemaId) || (schema.title && pattern.test(schema.title))) {
          return true
        }
      }
    }

    // Check for empty objects
    if (this.options.excludeEmptyObjects && this.isEmptyObjectSchema(schema)) {
      return true
    }

    // Check for conceptual markers
    if (this.options.excludeConceptualMarkers && this.isConceptualMarkerSchema(schema)) {
      return true
    }

    // Apply custom filter
    if (this.options.customExclusionFilter?.(schema, schemaId)) {
      return true
    }

    return false
  }

  /**
   * Checks if a schema represents an empty object with no meaningful structure.
   */
  private isEmptyObjectSchema(schema: JSONSchema7): boolean {
    return (
      schema.type === 'object' &&
      (!schema.properties || Object.keys(schema.properties).length === 0) &&
      !schema.additionalProperties &&
      !schema.patternProperties &&
      !schema.allOf &&
      !schema.oneOf &&
      !schema.anyOf &&
      !schema.if &&
      !schema.then &&
      !schema.else &&
      !schema.required?.length &&
      !schema.minProperties &&
      !schema.maxProperties
    )
  }

  /**
   * Checks if a schema is likely a conceptual marker with no structural value.
   */
  private isConceptualMarkerSchema(schema: JSONSchema7): boolean {
    // Check for schemas that are just empty objects or only have allOf with references
    if (this.isEmptyObjectSchema(schema)) {
      return true
    }

    // Note: We removed the check for single allOf with $ref as this represents
    // valid schema inheritance/extension patterns (e.g., BlogPosting extending SocialMediaPosting)
    // Such schemas should be included as they define meaningful type hierarchies

    // Check for schemas that only have description and type but no structure
    if (
      schema.type === 'object' &&
      schema.description &&
      !schema.properties &&
      !schema.additionalProperties &&
      !schema.allOf &&
      !schema.oneOf &&
      !schema.anyOf &&
      Object.keys(schema).filter((key) => !['$schema', '$id', 'title', 'description', 'type'].includes(key)).length ===
        0
    ) {
      return true
    }

    return false
  }

  /**
   * Gets a human-readable reason why a schema was excluded.
   */
  getExclusionReason(schema: JSONSchema7, schemaId?: string): string {
    if (schemaId && this.options.excludeSchemaIds?.includes(schemaId)) {
      return `Schema ID '${schemaId}' is in the exclusion list`
    }

    if (this.isEmptyObjectSchema(schema)) {
      return 'Schema is an empty object with no structural definition'
    }

    if (this.isConceptualMarkerSchema(schema)) {
      return 'Schema appears to be a conceptual marker with no concrete structure'
    }

    return 'Schema excluded by custom filter'
  }
}

/**
 * Predefined filtering strategies for common use cases.
 */
export const FILTERING_STRATEGIES = {
  /**
   * Strategy for API modeling - excludes schemas that don't contribute to API structure.
   */
  API_MODELING: new SchemaFilteringStrategy({
    excludeEmptyObjects: true,
    excludeConceptualMarkers: true,
    excludePatterns: [
      /.*Enumeration$/, // Exclude *Enumeration schemas
      /.*Type$/, // Exclude conceptual *Type schemas that are just markers
    ],
  }),

  /**
   * Strategy for database modeling - very strict, only includes schemas with concrete properties.
   */
  DATABASE_MODELING: new SchemaFilteringStrategy({
    excludeEmptyObjects: true,
    excludeConceptualMarkers: true,
    customExclusionFilter: (schema: JSONSchema7) => {
      // For database modeling, exclude anything that doesn't have properties or clear structure
      return (
        schema.type === 'object' &&
        !schema.properties &&
        !schema.additionalProperties &&
        !schema.allOf?.some(
          (subSchema) => typeof subSchema === 'object' && 'properties' in subSchema && subSchema.properties
        )
      )
    },
  }),

  /**
   * Conservative strategy - only excludes obviously empty schemas.
   */
  CONSERVATIVE: new SchemaFilteringStrategy({
    excludeEmptyObjects: true,
  }),
} as const
