import { GraphQLQuery, GraphQLEndpoint, associationSegment{% if milestone_class(cm.query.milestone) %}, {{milestone_class(cm.query.milestone)}}{% endif %} } from '@model/graphql_mapping';
import { NoOperation, Operation, RelationalOperationElement } from '@model/relational';
import { convertInputsAndSelect, convertInputsAndSelectForDateRange, FinderResult } from '@datafinder/runner';
import type { Attribute } from '@datafinder/attribute';
{% if attr_types %}
import { {{attr_types | join(', ')}} } from '@datafinder/typed-attributes';
{% endif %}
{% for target_cm in related_classes(cm) %}

/** Equality filters accepted when navigating to a {{target_cm.clazz.name}}. */
type {{target_cm.clazz.name}}Filter = {
{% for tpm in target_cm.property_mappings %}
{% if is_primitive_mapping(tpm) %}
  {{to_ts_name(tpm.property)}}?: {{filter_ts_type(tpm.property)}};
{% endif %}
{% endfor %}
};

class {{target_cm.clazz.name}}RelatedFinder {
  /** TypeScript property name -> GraphQL field, for the filters passed when navigating to this class. */
  static readonly FIELDS: Record<string, string> = {
{% for tpm in target_cm.property_mappings %}
{% if is_primitive_mapping(tpm) %}
    {{to_ts_name(tpm.property)}}: '{{tpm.target.name}}',
{% endif %}
{% endfor %}
  };

{% for tpm in target_cm.property_mappings %}
{% if is_primitive_mapping(tpm) %}
  private readonly _{{to_ts_name(tpm.property)}}: {{tpm.property.type.name}}Attribute;
{% endif %}
{% endfor %}

  constructor(private readonly parentField: string, private readonly displayPrefix: string) {
{% for tpm in target_cm.property_mappings %}
{% if is_primitive_mapping(tpm) %}
    this._{{to_ts_name(tpm.property)}} = new {{tpm.property.type.name}}Attribute(displayPrefix + ' {{display_name(tpm.property)}}', parentField + '.{{tpm.target.name}}', '{{type_str(tpm.property.type)}}', '{{cm.query.name}}');
{% endif %}
{% endfor %}
  }

{% for tpm in target_cm.property_mappings %}
{% if is_primitive_mapping(tpm) %}
  /** {{display_name(tpm.property)}} */
  {{to_ts_name(tpm.property)}}(): {{tpm.property.type.name}}Attribute {
    return this._{{to_ts_name(tpm.property)}};
  }

{% elif is_association_mapping(tpm) and class_mappings_by_name.get(tpm.property.type.name) %}
  /** {{display_name(tpm.property)}} */
  {{to_ts_name(tpm.property)}}(filter?: {{tpm.property.type.name}}Filter): {{tpm.property.type.name}}RelatedFinder {
    const segment = associationSegment('{{association_name(tpm)}}', {{association_many(tpm)}}, filter ?? {}, {{tpm.property.type.name}}RelatedFinder.FIELDS);
    return new {{tpm.property.type.name}}RelatedFinder(this.parentField + '.' + segment, this.displayPrefix + ' {{association_display(tpm)}}');
  }

{% endif %}
{% endfor %}
}
{% endfor %}

{% if cm.clazz.tagged_values['doc'] %}
/** {{cm.clazz.tagged_values['doc'].value}} */
{% endif %}
export class {{cm.clazz.name}}Finder {
  private readonly _query: GraphQLQuery;
{% for pm in cm.property_mappings %}
{% if is_primitive_mapping(pm) %}
  private readonly _{{to_ts_name(pm.property)}}: {{pm.property.type.name}}Attribute;
{% elif is_association_mapping(pm) and class_mappings_by_name.get(pm.property.type.name) %}
  private readonly _{{to_ts_name(pm.property)}}: {{pm.property.type.name}}RelatedFinder;
{% endif %}
{% endfor %}

  constructor(endpoint: GraphQLEndpoint) {
{% if cm.query.milestone %}
    this._query = new GraphQLQuery('{{cm.query.name}}', endpoint, {{milestone_expr(cm.query.milestone)}});
{% else %}
    this._query = new GraphQLQuery('{{cm.query.name}}', endpoint);
{% endif %}
{% for pm in cm.property_mappings %}
{% if is_primitive_mapping(pm) %}
    this._{{to_ts_name(pm.property)}} = new {{pm.property.type.name}}Attribute('{{display_name(pm.property)}}', '{{pm.target.name}}', '{{type_str(pm.property.type)}}', '{{cm.query.name}}');
{% elif is_association_mapping(pm) and class_mappings_by_name.get(pm.property.type.name) %}
    this._{{to_ts_name(pm.property)}} = new {{pm.property.type.name}}RelatedFinder('{{association_path(pm)}}', '{{association_display(pm)}}');
{% endif %}
{% endfor %}
  }

{% for pm in cm.property_mappings %}
{% if is_primitive_mapping(pm) %}
  /** {{display_name(pm.property)}} */
  {{to_ts_name(pm.property)}}(): {{pm.property.type.name}}Attribute {
    return this._{{to_ts_name(pm.property)}};
  }

{% elif is_association_mapping(pm) and class_mappings_by_name.get(pm.property.type.name) %}
  /** {{display_name(pm.property)}} */
  {{to_ts_name(pm.property)}}(filter?: {{pm.property.type.name}}Filter): {{pm.property.type.name}}RelatedFinder {
    if (filter) {
      const segment = associationSegment('{{association_name(pm)}}', {{association_many(pm)}}, filter, {{pm.property.type.name}}RelatedFinder.FIELDS);
      return new {{pm.property.type.name}}RelatedFinder(segment, '{{association_display(pm)}}');
    }
    return this._{{to_ts_name(pm.property)}};
  }

{% endif %}
{% endfor %}
  /** Returns data matching the optional filter. */
  findAll(
    businessDate: Date | string | null,
    processingValidAt: Date | string | null,
    displayColumns: (Attribute | RelationalOperationElement)[],
    filterOp: Operation = new NoOperation(),
  ): FinderResult {
    return convertInputsAndSelect(businessDate, processingValidAt, displayColumns, this._query, filterOp);
  }

  /** Returns data for all records whose business date falls within the given range. */
  findForDateRange(
    businessDateFrom: Date | string,
    businessDateTo: Date | string,
    processingValidAt: Date | string,
    displayColumns: (Attribute | RelationalOperationElement)[],
    filterOp: Operation = new NoOperation(),
  ): FinderResult {
    return convertInputsAndSelectForDateRange(businessDateFrom, businessDateTo, processingValidAt, displayColumns, this._query, filterOp);
  }
}
