/**
 * Multi-batch matcher for behavioral guidelines.
 *
 * Port of boocontext-audit/src/matching.ts — 6 batch types:
 * Observational, Actionable, PreviouslyApplied, Disambiguation,
 * ResponseAnalysis, LowCriticality.
 */

export type Criticality = 'low' | 'medium' | 'high';

export interface GuidelineContent {
  condition: string;
  action: string | null;
}

export interface Guideline {
  id: string;
  content: GuidelineContent;
  enabled: boolean;
  criticality: Criticality;
  priority: number;
  labels: string[];
  metadata: Record<string, unknown>;
  tags: string[];
  title: string | null;
}

export interface GenerationInfo {
  model: string;
  duration: number;
  tokens: number;
  temperature: number;
  attempt?: number;
}

export enum BatchType {
  Observational = 'observational',
  Actionable = 'actionable',
  PreviouslyApplied = 'previously_applied',
  Disambiguation = 'disambiguation',
  ResponseAnalysis = 'response_analysis',
  LowCriticality = 'low_criticality',
}

export interface GuidelineMatch {
  guideline: Guideline;
  score: number;
  rationale: string;
  metadata?: Record<string, unknown>;
}

export interface GuidelineMatchingContext {
  agent: string;
  session: string;
  customer: string;
  contextVariables: Record<string, string>[];
  interactionHistory: unknown[];
  terms: string[];
  capabilities?: string[];
  stagedEvents?: unknown[];
  activeJourneys?: unknown[];
  journeyPaths?: Record<string, unknown>;
}

export interface GuidelineMatchingBatchResult {
  matches: GuidelineMatch[];
  generationInfo: GenerationInfo;
}

export interface GuidelineMatchingResult {
  totalDuration: number;
  batchCount: number;
  batchGenerations: GenerationInfo[];
  batches: GuidelineMatch[][];
  matches: GuidelineMatch[];
}

export interface ObservationalGuidelineMatchSchema {
  guideline_id: string;
  condition: string;
  rationale: string;
  applies: boolean;
}

export interface ObservationalGuidelineMatchesSchema {
  checks: ObservationalGuidelineMatchSchema[];
}

export interface ActionableGuidelineMatchSchema {
  guideline_id: string;
  condition: string;
  action: string;
  rationale: string;
  applies: boolean;
}

export interface ActionableGuidelineMatchesSchema {
  checks: ActionableGuidelineMatchSchema[];
}

export interface PreviouslyAppliedGuidelineMatchSchema {
  guideline_id: string;
  condition: string;
  action_segment: string;
  rationale: string;
  is_still_applicable: boolean;
}

export interface PreviouslyAppliedGuidelineMatchesSchema {
  checks: PreviouslyAppliedGuidelineMatchSchema[];
}

export interface DisambiguationGuidelineMatchSchema {
  source_guideline_id: string;
  rationale: string;
  enriched_action: string;
  targets: string[];
}

export interface ResponseAnalysisSchema {
  guideline_id: string;
  condition: string;
  was_followed: boolean;
  rationale: string;
}

export interface ScoredMatch {
  guideline_id: string;
  score: number;
  rationale: string;
}

export class GuidelineMatchingBatchError extends Error {
  constructor(message = 'Guideline Matching Batch failed') {
    super(message);
    this.name = 'GuidelineMatchingBatchError';
  }
}

export interface GuidelineMatchingBatch {
  readonly size: number;
  process(): Promise<GuidelineMatchingBatchResult>;
}

export interface GuidelineMatchingStrategy {
  createMatchingBatches(
    guidelines: Guideline[],
    context: GuidelineMatchingContext,
  ): GuidelineMatchingBatch[];

  transformMatches(matches: GuidelineMatch[]): GuidelineMatch[];
}


export class ObservationalGuidelineMatchingBatch implements GuidelineMatchingBatch {
  constructor(
    public guidelines: Guideline[],
    public context: GuidelineMatchingContext,
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return this.guidelines.length;
  }

  async process(): Promise<GuidelineMatchingBatchResult> {
    const matches: GuidelineMatch[] = [];
    for (const g of this.guidelines) {
      if (g.content.action !== null && g.content.action !== undefined) continue;
      matches.push({
        guideline: g,
        score: 10,
        rationale: `Observational batch evaluated: "${g.content.condition}"`,
        metadata: { batch_type: BatchType.Observational },
      });
    }
    return { matches, generationInfo: this.generationInfo };
  }
}

export class ActionableGuidelineMatchingBatch implements GuidelineMatchingBatch {
  constructor(
    public guidelines: Guideline[],
    public context: GuidelineMatchingContext,
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return this.guidelines.length;
  }

  async process(): Promise<GuidelineMatchingBatchResult> {
    const matches: GuidelineMatch[] = [];
    for (const g of this.guidelines) {
      if (g.content.action === null || g.content.action === undefined) continue;
      if (g.content.action === '') continue;
      matches.push({
        guideline: g,
        score: 10,
        rationale: `Actionable batch evaluated: when "${g.content.condition}", then "${g.content.action}"`,
        metadata: { batch_type: BatchType.Actionable },
      });
    }
    return { matches, generationInfo: this.generationInfo };
  }
}

export class PreviouslyAppliedGuidelineMatchingBatch implements GuidelineMatchingBatch {
  constructor(
    public guidelines: Guideline[],
    public context: GuidelineMatchingContext,
    public priorMatches: GuidelineMatch[],
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return this.guidelines.length;
  }

  async process(): Promise<GuidelineMatchingBatchResult> {
    const alreadyApplied = new Set(
      this.priorMatches.filter((m) => m.score >= 10).map((m) => m.guideline.id),
    );
    const matches: GuidelineMatch[] = [];
    for (const g of this.guidelines) {
      if (alreadyApplied.has(g.id)) {
        matches.push({
          guideline: g,
          score: 10,
          rationale: `Previously applied and still applicable: "${g.content.condition}"`,
          metadata: { batch_type: BatchType.PreviouslyApplied },
        });
      }
    }
    return { matches, generationInfo: this.generationInfo };
  }
}

export class DisambiguationGuidelineMatchingBatch implements GuidelineMatchingBatch {
  constructor(
    public disambiguationGuideline: Guideline,
    public targets: Guideline[],
    public context: GuidelineMatchingContext,
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return 1 + this.targets.length;
  }

  async process(): Promise<GuidelineMatchingBatchResult> {
    const matches: GuidelineMatch[] = [];
    matches.push({
      guideline: this.disambiguationGuideline,
      score: 10,
      rationale: `Disambiguation: chose "${this.disambiguationGuideline.content.condition}" over targets`,
      metadata: {
        batch_type: BatchType.Disambiguation,
        disambiguation: {
          targets: this.targets.map((t) => t.id),
          enriched_action: this.disambiguationGuideline.content.action ?? '',
        },
      },
    });
    return { matches, generationInfo: this.generationInfo };
  }
}

export class ResponseAnalysisBatch {
  constructor(
    public guidelineMatches: GuidelineMatch[],
    public context: Record<string, unknown>,
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return this.guidelineMatches.length;
  }

  async process(): Promise<{ analyzed: unknown[]; generationInfo: GenerationInfo }> {
    const analyzed = this.guidelineMatches.map((m) => ({
      guideline: m.guideline,
      is_previously_applied: m.score >= 10,
    }));
    return { analyzed, generationInfo: this.generationInfo };
  }
}

export class LowCriticalityGuidelineMatchingBatch implements GuidelineMatchingBatch {
  constructor(
    public guidelines: Guideline[],
    public context: GuidelineMatchingContext,
    public generationInfo: GenerationInfo,
  ) {}

  get size(): number {
    return this.guidelines.length;
  }

  async process(): Promise<GuidelineMatchingBatchResult> {
    const matches: GuidelineMatch[] = [];
    for (const g of this.guidelines) {
      if (g.criticality !== 'low') continue;
      matches.push({
        guideline: g,
        score: g.content.action ? 10 : 1,
        rationale: `Low-criticality batch: "${g.content.condition}"`,
        metadata: { batch_type: BatchType.LowCriticality },
      });
    }
    return { matches, generationInfo: this.generationInfo };
  }
}

export class GenericGuidelineMatchingStrategy implements GuidelineMatchingStrategy {
  constructor(public generationInfo: GenerationInfo) {}

  createMatchingBatches(
    guidelines: Guideline[],
    context: GuidelineMatchingContext,
  ): GuidelineMatchingBatch[] {
    const observational: Guideline[] = [];
    const actionable: Guideline[] = [];
    const lowCriticality: Guideline[] = [];
    const disambiguationCandidates: Guideline[] = [];

    for (const g of guidelines) {
      if (g.criticality === 'low') {
        lowCriticality.push(g);
      } else if (!g.content.action) {
        disambiguationCandidates.push(g);
      } else if (g.content.action) {
        actionable.push(g);
      } else {
        observational.push(g);
      }
    }

    const batches: GuidelineMatchingBatch[] = [];

    if (observational.length > 0) {
      batches.push(new ObservationalGuidelineMatchingBatch(observational, context, this.generationInfo));
    }

    if (actionable.length > 0) {
      batches.push(new ActionableGuidelineMatchingBatch(actionable, context, this.generationInfo));
    }

    if (lowCriticality.length > 0) {
      batches.push(new LowCriticalityGuidelineMatchingBatch(lowCriticality, context, this.generationInfo));
    }

    return batches;
  }

  transformMatches(matches: GuidelineMatch[]): GuidelineMatch[] {
    const seen = new Set<string>();
    return matches.filter((m) => {
      const key = m.guideline.id;
      if (seen.has(key)) return false;
      seen.add(key);
      return true;
    });
  }
}

export async function matchWithRetry<T>(
  fn: () => Promise<T>,
  maxAttempts = 3,
  _baseTemperature = 0.7,
): Promise<T> {
  let lastError: unknown;
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastError = err;
      if (attempt < maxAttempts - 1) {
        // will retry
      }
    }
  }
  throw lastError;
}

export async function executeBatchesParallel(
  batches: GuidelineMatchingBatch[],
  _generationInfo: GenerationInfo,
): Promise<GuidelineMatchingResult> {
  const start = Date.now();
  const results = await Promise.all(
    batches.map((batch) => matchWithRetry(() => batch.process())),
  );

  const allBatches = results.map((r) => r.matches);
  const allMatches = allBatches.flat();
  const allGenInfos = results.map((r) => r.generationInfo);

  return {
    totalDuration: Date.now() - start,
    batchCount: batches.length,
    batchGenerations: allGenInfos,
    batches: allBatches,
    matches: allMatches,
  };
}

export function createScoredMatch(
  guidelineId: string,
  score: number,
  rationale: string,
): ScoredMatch {
  return { guideline_id: guidelineId, score, rationale };
}
