import type { ValueObject } from '@core/shared/domain/ValueObject';

/**
 * Incident types that can occur during a race
 */
export type IncidentType =
  | 'track_limits'        // Driver went off track and gained advantage
  | 'contact'             // Physical contact with another car
  | 'unsafe_rejoin'       // Unsafe rejoining of the track
  | 'aggressive_driving'  // Aggressive defensive or overtaking maneuvers
  | 'false_start'         // Started before green flag
  | 'collision'           // Major collision involving multiple cars
  | 'spin'                // Driver spun out
  | 'mechanical'          // Mechanical failure (not driver error)
  | 'other';              // Other incident types

/**
 * Individual incident record
 */
export interface IncidentRecord {
  type: IncidentType;
  lap: number;
  description?: string;
  penaltyPoints?: number; // Points deducted for this incident
}

/**
 * Value Object: RaceIncidents
 *
 * Encapsulates all incidents that occurred during a driver's race.
 * Provides methods to calculate total penalty points and incident severity.
 */
export class RaceIncidents implements ValueObject<IncidentRecord[]> {
  private readonly incidents: IncidentRecord[];

  constructor(incidents: IncidentRecord[] = []) {
    this.incidents = [...incidents];
  }

  get props(): IncidentRecord[] {
    return [...this.incidents];
  }

  /**
   * Add a new incident
   */
  addIncident(incident: IncidentRecord): RaceIncidents {
    return new RaceIncidents([...this.incidents, incident]);
  }

  /**
   * Get all incidents
   */
  getAllIncidents(): IncidentRecord[] {
    return [...this.incidents];
  }

  /**
   * Get total number of incidents
   */
  getTotalCount(): number {
    return this.incidents.length;
  }

  /**
   * Get total penalty points from all incidents
   */
  getTotalPenaltyPoints(): number {
    return this.incidents.reduce((total, incident) => total + (incident.penaltyPoints || 0), 0);
  }

  /**
   * Get incidents by type
   */
  getIncidentsByType(type: IncidentType): IncidentRecord[] {
    return this.incidents.filter(incident => incident.type === type);
  }

  /**
   * Check if driver had any incidents
   */
  hasIncidents(): boolean {
    return this.incidents.length > 0;
  }

  /**
   * Check if driver had a clean race (no incidents)
   */
  isClean(): boolean {
    return this.incidents.length === 0;
  }

  /**
   * Backwards-compatible helper for legacy "incident count" fields.
   * Creates `count` placeholder incidents of type `other`.
   */
  static fromLegacyIncidentsCount(count: number): RaceIncidents {
    if (!Number.isFinite(count) || count <= 0) {
      return new RaceIncidents();
    }

    const incidents: IncidentRecord[] = Array.from({ length: Math.floor(count) }, (_, index) => ({
      type: 'other',
      lap: index + 1,
      penaltyPoints: 0,
    }));

    return new RaceIncidents(incidents);
  }

  /**
   * A coarse severity score for incidents.
   * Kept intentionally data-light: derived only from `penaltyPoints`.
   */
  getSeverityScore(): number {
    return this.getTotalPenaltyPoints();
  }

  /**
   * Human-readable summary without hardcoded incident labels.
   */
  getSummary(): string {
    const total = this.getTotalCount();
    if (total === 0) return 'Clean race';

    const countsByType = new Map<IncidentType, number>();
    for (const incident of this.incidents) {
      countsByType.set(incident.type, (countsByType.get(incident.type) ?? 0) + 1);
    }

    const typeSummary = Array.from(countsByType.entries())
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([type, n]) => `${type}:${n}`)
      .join(', ');

    return typeSummary.length > 0 ? `${total} incidents (${typeSummary})` : `${total} incidents`;
  }

  equals(other: ValueObject<IncidentRecord[]>): boolean {
    const otherIncidents = other.props;
    if (this.incidents.length !== otherIncidents.length) {
      return false;
    }

    // Sort both arrays and compare
    const sortedThis = [...this.incidents].sort((a, b) => a.lap - b.lap);
    const sortedOther = [...otherIncidents].sort((a, b) => a.lap - b.lap);

    return sortedThis.every((incident, index) => {
      const otherIncident = sortedOther[index];
      return incident.type === otherIncident?.type &&
             incident.lap === otherIncident?.lap &&
             incident.description === otherIncident?.description &&
             incident.penaltyPoints === otherIncident?.penaltyPoints;
    });
  }

}