/**
 * Sales Script AI Pipeline
 *
 * Orchestrates a 3-stage AI pipeline for generating multiple sales scripts.
 * Uses company context, ICP data, product info, and brand strategy as seed input.
 * Follows the multi-item pattern (like Testimonial pipeline) with pre-allocated
 * array and per-index merge across stages.
 */

import { generateWithAI } from '../../utils/aiProvider';
import { parseJsonFromAI } from './parseJsonFromAI';
import {
  buildSalesScriptIdentityPrompt,
  buildSalesScriptContentPrompt,
  buildSalesScriptTrainingPrompt,
  buildSalesScriptEnhancementPrompt,
  buildSalesScriptRegenerationPrompt,
  buildSalesScriptBatchRegenerationPrompt,
  SalesScriptPipelineInputs,
  SalesAngle,
  PromptResult,
  SALES_ANGLES,
  DuplicateScriptInfo,
} from './salesScriptPrompts';

// ============================================
// TYPES
// ============================================

export interface SalesScriptPipelineResult {
  scripts: Record<string, any>[];
  pipelineVersion: string;
  provider: string;
  aiModel: string;
  tokensUsed: number;
  inputTokens: number;
  outputTokens: number;
  processingTimeMs: number;
  latencyMs: number;
  overallConfidence: number;
  finishReason: string | null;
  apiKeyMasked: string | null;
  stageResults: StageResult[];
  errors: string[];
  dedupResults?: {
    attempts: number;
    duplicatesFound: number;
    scriptsRegenerated: number[];
  };
}

export interface StageResult {
  stage: string;
  success: boolean;
  provider?: string;
  aiModel?: string;
  tokensUsed?: number;
  error?: string;
  duration: number;
  enhanced?: boolean;
}

const PIPELINE_VERSION = '1.0';
const CONFIDENCE_THRESHOLD = 60;

// ============================================
// DUPLICATE DETECTION CONSTANTS
// ============================================

export const DUPLICATE_CHECK_FIELDS = ['openingLine', 'hook', 'valueProposition'] as const;

/**
 * Language-aware similarity threshold.
 * Non-English languages (especially Hindi/Devanagari) have less vocabulary variation
 * and shorter words, which can cause higher similarity scores even for distinct content.
 */
export const DEFAULT_SIMILARITY_THRESHOLD = 0.75; // 75% for English
export const NON_ENGLISH_SIMILARITY_THRESHOLD = 0.65; // 65% for non-English

export function getSimilarityThreshold(language?: string): number {
  if (!language) return DEFAULT_SIMILARITY_THRESHOLD;
  const lang = language.toLowerCase();
  // Languages with limited vocabulary variation need lower thresholds
  if (lang === 'hindi' || lang === 'हिंदी' || lang === 'marathi' || lang === 'मराठी' || lang === 'bengali' || lang === 'tamil' || lang === 'telugu') {
    return NON_ENGLISH_SIMILARITY_THRESHOLD;
  }
  // Default for English and other languages
  if (lang === 'english') return DEFAULT_SIMILARITY_THRESHOLD;
  // Unknown language - use slightly lower threshold to be safe
  return 0.70;
}

const MAX_REGENERATION_ATTEMPTS = 1;

interface DuplicateReport {
  scriptIndex: number;
  duplicateOf: number;
  field: string;
  similarity: number;
}

// ============================================
// STRING SIMILARITY (Jaccard on word bigrams)
// ============================================

export function computeSimilarity(a: string, b: string): number {
  if (!a || !b) return 0;
  if (a === b) return 1;

  // Unicode-aware normalization: keep letters (\p{L}), numbers (\p{N}), and whitespace
  // The 'u' flag enables Unicode property escapes for non-ASCII scripts (Devanagari, etc.)
  const normalize = (s: string) =>
    s.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/g, ' ').trim();

  const toBigrams = (s: string): Set<string> => {
    const words = normalize(s).split(' ').filter(w => w.length > 0);
    const bigrams = new Set<string>();
    for (let i = 0; i < words.length - 1; i++) {
      bigrams.add(`${words[i]} ${words[i + 1]}`);
    }
    return bigrams;
  };

  const setA = toBigrams(a);
  const setB = toBigrams(b);
  if (setA.size === 0 && setB.size === 0) return 1;
  if (setA.size === 0 || setB.size === 0) return 0;

  let intersection = 0;
  for (const item of setA) {
    if (setB.has(item)) intersection++;
  }
  const union = setA.size + setB.size - intersection;
  return union === 0 ? 0 : intersection / union;
}

// ============================================
// JSON PARSING UTILITIES
// ============================================

function extractFieldsFromRawContent(content: string): Record<string, any> {
  const result: Record<string, any> = {};
  if (!content || typeof content !== 'string') return result;

  const stringPairRegex = /"(\w+)":\s*"((?:[^"\\]|\\.)*)"/g;
  let match;
  while ((match = stringPairRegex.exec(content)) !== null) {
    const [, key, value] = match;
    result[key] = value.replace(/\\"/g, '"').replace(/\\n/g, '\n');
  }

  const arrayPairRegex = /"(\w+)":\s*\[((?:\s*"(?:[^"\\]|\\.)*"\s*,?\s*)+)\]/g;
  while ((match = arrayPairRegex.exec(content)) !== null) {
    const [, key, arrayContent] = match;
    const values = [...arrayContent.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map(m => m[1]);
    if (values.length > 0) {
      result[key] = values;
    }
  }

  const nestedObjRegex = /"(\w+)":\s*(\{[^}]*\})/g;
  while ((match = nestedObjRegex.exec(content)) !== null) {
    const [, key, objStr] = match;
    try {
      result[key] = JSON.parse(objStr);
    } catch {
      // Skip unparseable nested objects
    }
  }

  return result;
}

function extractScriptArray(parsed: Record<string, any>): any[] {
  if (Array.isArray(parsed)) return parsed;
  if (parsed.scripts && Array.isArray(parsed.scripts)) return parsed.scripts;
  if (parsed.testimonials && Array.isArray(parsed.testimonials)) return parsed.testimonials;
  // Single object wrap
  return [parsed];
}

// ============================================
// PIPELINE CLASS
// ============================================

export class SalesScriptPipeline {
  private inputs: SalesScriptPipelineInputs;
  private targetCount: number;
  private scriptAccumulated: Record<string, any>[];
  private stageResults: StageResult[];
  private errors: string[];
  private totalTokens: number;
  private totalInputTokens: number;
  private totalOutputTokens: number;
  private totalLatencyMs: number;
  private lastFinishReason: string | null;
  private lastApiKeyMasked: string | null;
  private startTime: number;
  private lastProvider: string;
  private lastAiModel: string;
  private onProgress?: (progress: number, step: string) => void;
  private preferredProvider?: 'ollama' | 'zhipu' | 'claude' | 'openai' | 'auto';

  constructor(inputs: SalesScriptPipelineInputs, onProgress?: (progress: number, step: string) => void, preferredProvider?: 'ollama' | 'zhipu' | 'claude' | 'openai' | 'auto') {
    this.inputs = inputs;
    this.targetCount = Math.min(inputs.targetCount || 10, 10);
    this.scriptAccumulated = Array.from({ length: this.targetCount }, () => ({}));
    this.stageResults = [];
    this.errors = [];
    this.totalTokens = 0;
    this.totalInputTokens = 0;
    this.totalOutputTokens = 0;
    this.totalLatencyMs = 0;
    this.lastFinishReason = null;
    this.lastApiKeyMasked = null;
    this.startTime = Date.now();
    this.preferredProvider = preferredProvider;
    this.lastProvider = 'unknown';
    this.lastAiModel = 'unknown';
    this.onProgress = onProgress;
  }

  async run(): Promise<SalesScriptPipelineResult> {
    // Stage 1: Script Identity & Strategy
    this.onProgress?.(5, 'Creating script identities...');
    await this.runStageWithRetry('script-identity', () =>
      this.executeStage(
        buildSalesScriptIdentityPrompt(this.inputs),
        'identity',
        0
      )
    );
    this.onProgress?.(35, 'Script identities defined');

    // Stage 2: Script Content & Structure
    this.onProgress?.(40, 'Generating script content...');
    await this.runStageWithRetry('script-content', () =>
      this.executeStage(
        buildSalesScriptContentPrompt(this.inputs, this.scriptAccumulated),
        'content',
        1
      )
    );
    this.onProgress?.(55, 'Script content generated');

    // Duplicate detection and regeneration
    this.onProgress?.(60, 'Checking for duplicate content...');
    const dedupResults = await this.regenerateDuplicateScripts(1);
    this.onProgress?.(70, 'Duplicate check complete');

    // Stage 3: Training & Best Practices
    this.onProgress?.(75, 'Adding training & best practices...');
    await this.runStageWithRetry('script-training', () =>
      this.executeStage(
        buildSalesScriptTrainingPrompt(this.inputs, this.scriptAccumulated),
        'training',
        2
      )
    );
    this.onProgress?.(90, 'Finalizing scripts');

    // Validate key fields are populated
    const validationErrors = this.validateScripts();
    if (validationErrors.length > 0) {
      this.errors.push(`Missing or insufficient content for: ${validationErrors.join(', ')}`);
    }

    const overallConfidence = this.computeOverallConfidence();

    return {
      scripts: this.scriptAccumulated,
      pipelineVersion: PIPELINE_VERSION,
      provider: this.lastProvider,
      aiModel: this.lastAiModel,
      tokensUsed: this.totalTokens,
      inputTokens: this.totalInputTokens,
      outputTokens: this.totalOutputTokens,
      processingTimeMs: Date.now() - this.startTime,
      latencyMs: this.totalLatencyMs,
      overallConfidence,
      finishReason: this.lastFinishReason,
      apiKeyMasked: this.lastApiKeyMasked,
      stageResults: this.stageResults,
      errors: this.errors,
      dedupResults,
    };
  }

  // ============================================
  // STAGE EXECUTION
  // ============================================

  private async executeStage(promptConfig: PromptResult, stageName: string, stageIndex: number): Promise<void> {
    console.log(`[SalesScript-Pipeline] Executing stage "${stageName}" with ${promptConfig.maxTokens} maxTokens`);

    const result = await generateWithAI(
      promptConfig.userPrompt,
      promptConfig.systemPrompt,
      promptConfig.maxTokens,
      undefined,
      undefined,
      this.preferredProvider
    );

    this.lastProvider = result.provider;
    this.lastAiModel = result.model;
    this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
    this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
    this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
    if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
    if (result.finishReason) this.lastFinishReason = result.finishReason;
    if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;

    console.log(`[SalesScript-Pipeline] Stage "${stageName}" response received. Provider: ${result.provider}, Content length: ${result.content?.length || 0}`);
    console.log(`[SalesScript-Pipeline] Stage "${stageName}" raw response (first 500 chars): ${result.content?.substring(0, 500)}`);

    const parsed = parseJsonFromAI(result.content);
    if (!parsed) {
      // Fallback: extract fields from raw content (like testimonial/landing-page pipelines)
      const extracted = extractFieldsFromRawContent(result.content);
      if (Object.keys(extracted).length > 0) {
        console.warn(`[SalesScript-Pipeline] Stage "${stageName}": JSON parsing failed, extracted ${Object.keys(extracted).length} fields as single script`);
        this.mergeIntoScript(0, extracted);
        return;
      }
      throw new Error(`AI response could not be parsed as JSON for stage: ${stageName}`);
    }

    const scripts = extractScriptArray(parsed);
    console.log(`[SalesScript-Pipeline] Stage "${stageName}" extracted ${scripts.length} scripts`);

    // Merge each parsed script into its corresponding slot
    for (let i = 0; i < scripts.length && i < this.targetCount; i++) {
      this.mergeIntoScript(i, scripts[i]);
    }

    // If fewer scripts returned than target, fill remaining with varied variants
    // (NOT identical clones — each variant gets differentiated content to avoid 100% duplicate detection)
    if (scripts.length < this.targetCount && scripts.length > 0) {
      const variantAngles = [
        'a different angle focusing on relationship building and trust',
        'an alternative approach emphasizing ROI and measurable outcomes',
        'a fresh perspective highlighting urgency and market timing',
        'a consultative angle asking thought-provoking questions first',
        'a storytelling approach using customer success narratives',
        'a challenger perspective presenting counterintuitive insights',
        'a value-first approach leading with concrete numbers and savings',
        'a problem-solution angle naming the pain point upfront',
      ];
      for (let i = scripts.length; i < this.targetCount; i++) {
        const base = { ...scripts[i % scripts.length] };
        const variantNum = i - scripts.length + 2;
        const angle = variantAngles[(i - scripts.length) % variantAngles.length];
        base.title = base.title ? `${base.title} (Variant ${variantNum})` : `Script Variant ${i + 1}`;
        // Vary key content fields so dedup detection doesn't flag them as 100% identical
        if (base.openingLine) base.openingLine = `[Variant ${variantNum} — ${angle}] ${base.openingLine}`;
        if (base.hook) base.hook = `[Variant ${variantNum}] ${base.hook}`;
        if (base.valueProposition) base.valueProposition = `[Variant ${variantNum}] ${base.valueProposition}`;
        if (base.closingCTA) base.closingCTA = base.closingCTA.replace(/\?$/, ` — Variant ${variantNum}?`) || `${base.closingCTA} (Variant ${variantNum})`;
        this.mergeIntoScript(i, base);
      }
    }
  }

  private async runStageWithRetry(stageName: string, stageFn: () => Promise<void>): Promise<void> {
    const stageStart = Date.now();

    try {
      await stageFn();
      this.stageResults.push({
        stage: stageName,
        success: true,
        provider: this.lastProvider,
        aiModel: this.lastAiModel,
        duration: Date.now() - stageStart,
      });
    } catch (error: any) {
      this.errors.push(`Stage ${stageName} failed: ${error.message}`);
      this.stageResults.push({
        stage: stageName,
        success: false,
        error: error.message,
        duration: Date.now() - stageStart,
      });
      return;
    }

    // Enhancement retry for low confidence
    const lowFields = this.getLowConfidenceFields(stageName);
    if (lowFields.length > 0) {
      await this.enhanceStage(stageName, lowFields);
    }
  }

  // ============================================
  // ENHANCEMENT RETRY
  // ============================================

  private async enhanceStage(stageName: string, lowConfidenceFields: string[]): Promise<void> {
    const enhanceStart = Date.now();

    try {
      const stageOutput = { scripts: this.scriptAccumulated };
      const enhancePrompt = buildSalesScriptEnhancementPrompt(stageName, stageOutput, lowConfidenceFields);

      const result = await generateWithAI(
        enhancePrompt.userPrompt,
        enhancePrompt.systemPrompt,
        enhancePrompt.maxTokens,
        undefined,
        undefined,
        this.preferredProvider
      );

      this.totalTokens += result.tokenUsage?.totalTokens ?? 0;
      this.totalInputTokens += result.tokenUsage?.inputTokens ?? 0;
      this.totalOutputTokens += result.tokenUsage?.outputTokens ?? 0;
      if (result.latencyMs) this.totalLatencyMs += result.latencyMs;
      if (result.finishReason) this.lastFinishReason = result.finishReason;
      if (result.keyUsed) this.lastApiKeyMasked = result.keyUsed;
      const parsed = parseJsonFromAI(result.content);
      if (parsed) {
        const scripts = extractScriptArray(parsed);
        for (let i = 0; i < scripts.length && i < this.targetCount; i++) {
          this.mergeIntoScript(i, scripts[i]);
        }
        this.stageResults.push({
          stage: `${stageName}-enhancement`,
          success: true,
          provider: result.provider,
          aiModel: result.model,
          duration: Date.now() - enhanceStart,
          enhanced: true,
        });
      }
    } catch (error: any) {
      console.warn(`[SalesScript-Pipeline] Enhancement for ${stageName} failed: ${error.message}`);
    }
  }

  // ============================================
  // RESULT MERGING
  // ============================================

  private mergeIntoScript(index: number, parsed: Record<string, any>): void {
    if (!parsed || typeof parsed !== 'object') return;
    for (const [key, value] of Object.entries(parsed)) {
      if (value !== null && value !== undefined) {
        const existing = this.scriptAccumulated[index][key];
        if (existing === undefined || existing === null || existing === '' || (Array.isArray(existing) && existing.length === 0)) {
          this.scriptAccumulated[index][key] = value;
        } else if (Array.isArray(value) && value.length > 0 && Array.isArray(existing)) {
          if (value.length > existing.length) {
            this.scriptAccumulated[index][key] = value;
          }
        }
      }
    }
  }

  // ============================================
  // CONFIDENCE ANALYSIS
  // ============================================

  private getLowConfidenceFields(stageName: string): string[] {
    const low: string[] = [];
    const stageCriticalFields: Record<string, string[]> = {
      'script-identity': ['title', 'scriptType', 'funnelStage'],
      'script-content': ['openingLine', 'sections'],
      'script-training': ['trainingNotes', 'bestPractices'],
    };

    const criticalFields = stageCriticalFields[stageName] || [];
    for (const field of criticalFields) {
      const missingCount = this.scriptAccumulated.filter(s => !s[field] || s[field] === '' || (Array.isArray(s[field]) && s[field].length === 0)).length;
      if (missingCount > this.targetCount / 2) {
        low.push(field);
      }
    }

    return low;
  }

  private computeOverallConfidence(): number {
    const successCount = this.stageResults.filter(s => s.success && !s.enhanced).length;
    const successRate = successCount / 3;
    return Math.round(successRate * 100);
  }

  // ============================================
  // DUPLICATE DETECTION & REGENERATION
  // ============================================

  private detectDuplicates(): DuplicateReport[] {
    const reports: DuplicateReport[] = [];

    for (let i = 0; i < this.scriptAccumulated.length; i++) {
      for (let j = i + 1; j < this.scriptAccumulated.length; j++) {
        for (const field of DUPLICATE_CHECK_FIELDS) {
          const valA = this.scriptAccumulated[i][field];
          const valB = this.scriptAccumulated[j][field];
          if (!valA || !valB) continue;

          const similarity = computeSimilarity(String(valA), String(valB));
          if (similarity >= DEFAULT_SIMILARITY_THRESHOLD) {
            reports.push({
              scriptIndex: j,
              duplicateOf: i,
              field,
              similarity,
            });
          }
        }
      }
    }

    return reports;
  }

  private async regenerateDuplicateScripts(attempt: number): Promise<SalesScriptPipelineResult['dedupResults']> {
    const reports = this.detectDuplicates();

    if (reports.length === 0) {
      return { attempts: attempt - 1, duplicatesFound: 0, scriptsRegenerated: [] };
    }

    if (attempt > MAX_REGENERATION_ATTEMPTS) {
      console.warn(
        `[SalesScript-Pipeline] Duplicate content still detected after ${MAX_REGENERATION_ATTEMPTS} regeneration attempts. ` +
        `Duplicate fields: ${reports.map(r => `Script ${r.scriptIndex}.${r.field} ~${Math.round(r.similarity * 100)}% similar to Script ${r.duplicateOf}`).join('; ')}`
      );
      return {
        attempts: attempt - 1,
        duplicatesFound: reports.length,
        scriptsRegenerated: [],
      };
    }

    const indicesToRegenerate = [...new Set(reports.map(r => r.scriptIndex))].sort((a, b) => a - b);
    console.log(`[SalesScript-Pipeline] Regeneration attempt ${attempt}: regenerating scripts ${indicesToRegenerate.join(', ')} due to duplicate content`);

    // Build duplicate info for batch regeneration
    const duplicateInfos: DuplicateScriptInfo[] = indicesToRegenerate.map((scriptIndex) => {
      const existing = this.scriptAccumulated[scriptIndex];
      const angle = SALES_ANGLES[scriptIndex % SALES_ANGLES.length];

      const avoidContent: Record<string, string[]> = {};
      for (const field of DUPLICATE_CHECK_FIELDS) {
        avoidContent[field] = [];
        for (const report of reports) {
          if (report.scriptIndex === scriptIndex && report.field === field) {
            const originalValue = String(this.scriptAccumulated[report.duplicateOf][field] || '');
            if (originalValue) {
              avoidContent[field].push(originalValue);
            }
          }
        }
      }

      return { scriptIndex, existingScript: existing, angle, avoidContent };
    });

    // Try batch regeneration first (single AI call for all duplicates)
    try {
      const batchPrompt = buildSalesScriptBatchRegenerationPrompt(this.inputs, duplicateInfos);
      const batchResult = await generateWithAI(
        batchPrompt.userPrompt,
        batchPrompt.systemPrompt,
        batchPrompt.maxTokens,
        undefined,
        undefined,
        this.preferredProvider
      );

      const parsed = parseJsonFromAI(batchResult.content);
      if (parsed) {
        const scripts = extractScriptArray(parsed);
        if (scripts.length > 0) {
          for (let i = 0; i < scripts.length && i < duplicateInfos.length; i++) {
            this.overwriteScriptFields(duplicateInfos[i].scriptIndex, scripts[i], [
              'openingLine', 'hook', 'valueProposition',
              'offerPresentation', 'closingCTA', 'followUpCTA',
              'exitResponse', 'messagingGuidelines',
            ]);
          }
          console.log(`[SalesScript-Pipeline] Batch regeneration succeeded for ${Math.min(scripts.length, duplicateInfos.length)} scripts`);
          // Re-check for remaining duplicates
          return this.regenerateDuplicateScripts(attempt + 1);
        }
      } else {
        // Fallback: extract fields from raw content
        const extracted = extractFieldsFromRawContent(batchResult.content);
        if (Object.keys(extracted).length > 0 && duplicateInfos.length === 1) {
          this.overwriteScriptFields(duplicateInfos[0].scriptIndex, extracted, [
            'openingLine', 'hook', 'valueProposition',
            'offerPresentation', 'closingCTA', 'followUpCTA',
            'exitResponse', 'messagingGuidelines',
          ]);
        }
      }
    } catch (error: any) {
      console.warn(`[SalesScript-Pipeline] Batch regeneration failed: ${error.message}`);
    }

    // Re-check for remaining duplicates
    return this.regenerateDuplicateScripts(attempt + 1);
  }

  private overwriteScriptFields(index: number, parsed: Record<string, any>, fields: string[]): void {
    if (!parsed || typeof parsed !== 'object') return;
    for (const key of fields) {
      if (parsed[key] !== null && parsed[key] !== undefined) {
        this.scriptAccumulated[index][key] = parsed[key];
      }
    }
  }

  // ============================================
  // VALIDATION
  // ============================================

  private validateScripts(): string[] {
    const missingFields: string[] = [];
    const requiredFields = ['openingLine', 'hook', 'valueProposition'] as const;

    for (let i = 0; i < this.scriptAccumulated.length; i++) {
      for (const field of requiredFields) {
        const value = this.scriptAccumulated[i][field];
        if (!value || (typeof value === 'string' && value.trim().length < 10)) {
          missingFields.push(`Script ${i + 1}.${field}`);
        }
      }
    }

    return missingFields;
  }
}

// ============================================
// EXPORTED DEDUP UTILITIES
// ============================================

/**
 * Check if a candidate script is a duplicate of any script in an existing array.
 * Compares the specified fields using Jaccard similarity on word bigrams.
 * Uses language-aware threshold to account for vocabulary differences.
 */
export function isDuplicateOfAny(
  candidate: Record<string, any>,
  existingScripts: Record<string, any>[],
  fields: readonly string[] = DUPLICATE_CHECK_FIELDS,
  threshold: number = DEFAULT_SIMILARITY_THRESHOLD,
  options?: { language?: string; logDetails?: boolean }
): boolean {
  const effectiveThreshold = options?.language
    ? getSimilarityThreshold(options.language)
    : threshold;

  for (let i = 0; i < existingScripts.length; i++) {
    const script = existingScripts[i];
    for (const field of fields) {
      const valA = String(candidate[field] || '');
      const valB = String(script[field] || '');
      if (valA && valB) {
        const similarity = computeSimilarity(valA, valB);
        if (similarity >= effectiveThreshold) {
          if (options?.logDetails !== false) {
            console.log(`[Dedup] DUPLICATE DETECTED:`);
            console.log(`  Field: ${field}`);
            console.log(`  Similarity: ${(similarity * 100).toFixed(1)}% (threshold: ${(effectiveThreshold * 100).toFixed(0)}%)`);
            console.log(`  Language: ${options?.language || 'English'}`);
            console.log(`  Candidate (${field.substring(0, 50)}...): "${valA.substring(0, 80)}..."`);
            console.log(`  Existing (${field.substring(0, 50)}...): "${valB.substring(0, 80)}..."`);
          }
          return true;
        }
      }
    }
  }
  return false;
}