import type { ParseNeedResult, ParsedNeedCriteria, FollowUpQuestion } from '../../../domain/needBuilder'
import type { AssetType } from '../../../domain/enums'

export function mockParseNeed(input: string): ParseNeedResult {
  const lower = input.toLowerCase()

  // Asset type — RETAIL before LOGISTICS to avoid false match on "Nebenräume (Lager)"
  const assetType: AssetType | undefined =
    lower.includes('retail') || lower.includes('laden') || lower.includes('shop')
      || lower.includes('verkaufslokal') || lower.includes('ladenlokal') || lower.includes('ladenfläche')
      || lower.includes('verkaufsfläche') || lower.includes('schaufenster') ? 'RETAIL'
    : lower.includes('büro') || lower.includes('office') || lower.includes('sitzungszimmer') || lower.includes('arbeitsplätze') ? 'OFFICE'
    : lower.includes('logistik') || lower.includes('lagerhalle') || lower.includes('lagerraum')
      || (lower.includes('lager') && (lower.includes('logistik') || lower.includes('rampe') || lower.includes('palette') || lower.includes('lkw'))) ? 'LOGISTICS'
    : lower.includes('lager') ? 'LOGISTICS'
    : lower.includes('produktion') || lower.includes('gewerbe') || lower.includes('industrie') ? 'PRODUCTION'
    : lower.includes('gastro') || lower.includes('restaurant') ? 'GASTRO'
    : undefined

  // Area
  const areaRangeMatch = input.match(/(\d+)\s*[–\-–]\s*(\d+)\s*m[²2]/i)
  const areaSingleMatch = input.match(/(\d{3,5})\s*m[²2]/i)
  let areaRange: { min: number; max: number } | undefined
  let areaConfidence = 0.25
  if (areaRangeMatch) {
    areaRange = { min: parseInt(areaRangeMatch[1]), max: parseInt(areaRangeMatch[2]) }
    areaConfidence = 0.95
  } else if (areaSingleMatch) {
    const base = parseInt(areaSingleMatch[1])
    areaRange = { min: Math.round(base * 0.8), max: Math.round(base * 1.2) }
    areaConfidence = 0.70
  }

  // Locations — use word boundaries to avoid false matches like "Umzugsfirma" → "Zug"
  const CITIES: [string, string][] = [
    ['zürich', 'Zürich'], ['basel', 'Basel'], ['bern', 'Bern'], ['genf', 'Genf'],
    ['lausanne', 'Lausanne'], ['winterthur', 'Winterthur'], ['zug', 'Zug'],
    ['luzern', 'Luzern'], ['st. gallen', 'St. Gallen'], ['lugano', 'Lugano'],
    ['biel', 'Biel'], ['schaffhausen', 'Schaffhausen'],
    ['wädenswil', 'Wädenswil'], ['thalwil', 'Thalwil'], ['uster', 'Uster'],
    ['horgen', 'Horgen'], ['küsnacht', 'Küsnacht'], ['baar', 'Baar'],
  ]
  const preferredLocations = CITIES.filter(([k]) => {
    if (k.includes(' ') || k.includes('.')) return lower.includes(k)
    // Word boundary: not preceded/followed by a letter (including German umlauts)
    const re = new RegExp(`(?<![a-zA-ZäöüÄÖÜß])${k}(?![a-zA-ZäöüÄÖÜß])`)
    return re.test(lower)
  }).map(([, v]) => v)

  // Infer Zürich when Zürich-specific districts or landmarks are mentioned.
  // "kreis N" patterns require a word boundary so "umkreis 22" does NOT match "kreis 2".
  const ZURICH_SIGNAL_WORDS = [
    'seefeld', 'bellevue', 'paradeplatz', 'bahnhofstrasse', 'zürich-west', 'zürich west',
    'oerlikon', 'altstetten', 'langstrasse', 'hardbrücke', 'freilager',
    'europaallee', 'zürich nord', 'zürich süd',
  ]
  const ZURICH_KREIS_RE = /(?<![a-zA-ZäöüÄÖÜß])kreis\s+[1-8](?![0-9a-zA-ZäöüÄÖÜß])/
  if (!preferredLocations.includes('Zürich') && (
    ZURICH_SIGNAL_WORDS.some(s => lower.includes(s)) || ZURICH_KREIS_RE.test(lower)
  )) {
    preferredLocations.push('Zürich')
  }

  // Contextual Zürich inference: only truly Zürich-specific place/brand names
  const ZURICH_ONLY_SIGNALS = [
    'pfingstweidstrasse', 'freilager', 'europaallee',
    'zürich-west', 'zürich west', 'zürich nord', 'zürich süd',
    'hürlimann', 'viadukt', 'schiffbau',
  ]
  if (preferredLocations.length === 0 && ZURICH_ONLY_SIGNALS.some(s => lower.includes(s))) {
    preferredLocations.push('Zürich')
  }

  // Extract district list from patterns like "Kreis 3, 4, 5, und 8" or "Kreis 3/4/5"
  const kreisListMatch = lower.match(/\bkreis\s+([\d]+(?:\s*[,\/]\s*[\d]+)*(?:\s+und\s+[\d]+)?)/)
  if (kreisListMatch) {
    const nums = kreisListMatch[1].match(/\d+/g) ?? []
    nums.forEach(n => {
      const k = `Zürich Kreis ${n}`
      if (!preferredLocations.includes(k)) preferredLocations.push(k)
    })
    if (!preferredLocations.includes('Zürich')) preferredLocations.push('Zürich')
  }

  // Map Zürich landmark/neighbourhood names → district-level location entries
  const ZURICH_DISTRICT_MAP: [string, string][] = [
    ['seefeld', 'Zürich Seefeld'], ['bellevue', 'Zürich Seefeld'],
    ['bahnhofstrasse', 'Zürich Kreis 1'], ['paradeplatz', 'Zürich Kreis 1'],
    ['zürich-west', 'Zürich-West'], ['pfingstweidstrasse', 'Zürich-West'],
    ['limmatstrasse', 'Zürich-West'],
  ]
  ZURICH_DISTRICT_MAP.forEach(([k, v]) => {
    if (lower.includes(k) && !preferredLocations.includes(v)) preferredLocations.push(v)
  })

  const locationConfidence = preferredLocations.length > 0 ? 0.88 : 0.15

  // Budget — strip Swiss price notation (320.--) before matching
  const cleanedBudget = input.replace(/(\d)\.-+/g, '$1').replace(/(\d)\.—/g, '$1')
  // "MZ 320/m²", "CHF 320/m²", "320/m²", "320.--/m²/a"
  const budgetPerSqmMatch = cleanedBudget.match(/(?:mz|mietzins|max\.?|budget)?\s*(?:CHF\s*)?(\d{2,4})\s*\/\s*m[²2]/i)
  // Range "250 – 280 CHF" or "250-280/m²" — require explicit currency or /m² so area ranges like "120–150m2" are not matched
  const budgetRangeMatch =
    cleanedBudget.match(/(\d{2,4})\s*[–\-]\s*(\d{2,4})\s*(?:CHF|Fr\.?)\b/i) ??
    cleanedBudget.match(/(?:CHF|Fr\.?)\s*(\d{2,4})\s*[–\-]\s*(\d{2,4})/i) ??
    cleanedBudget.match(/(\d{2,4})\s*[–\-]\s*(\d{2,4})\s*\/\s*m[²2]/i)
  const budgetMaxMatch = input.match(/(?:max\.?|bis|höchstens)\s*(?:CHF\s*)?(\d+)/i)
  let budgetRange: { maxPerSqm: number; currency: string } | undefined
  let budgetConfidence = 0.20
  // Monthly-to-annual threshold: Swiss retail quotes monthly (150/m²/mon → 1800/year),
  // office/logistics quote monthly too but at lower values (45/m²/mon → 540/year).
  const monthlyThreshold = assetType === 'RETAIL' ? 500 : 150
  if (budgetPerSqmMatch) {
    const raw = parseInt(budgetPerSqmMatch[1])
    budgetRange = { maxPerSqm: raw < monthlyThreshold ? raw * 12 : raw, currency: 'CHF' }
    budgetConfidence = 0.92
  } else if (budgetRangeMatch) {
    // Take upper value of range as max
    const raw = parseInt(budgetRangeMatch[2])
    budgetRange = { maxPerSqm: raw < monthlyThreshold ? raw * 12 : raw, currency: 'CHF' }
    budgetConfidence = 0.75
  } else if (budgetMaxMatch) {
    const raw = parseInt(budgetMaxMatch[1])
    budgetRange = { maxPerSqm: raw < monthlyThreshold ? raw * 12 : raw, currency: 'CHF' }
    budgetConfidence = 0.60
  }

  // Timing
  const yearMatch = input.match(/(?:ab\s+)?(?:Q[1-4]\s*\/?\s*)?(20\d{2})/)
  // "Q4/26" or "Q1/27" — two-digit year (collect all occurrences, use first as earliest)
  const quarterMatches = [...input.matchAll(/Q([1-4])\s*[\/\-]?\s*(\d{2})\b/gi)]
  const soonMatch = lower.includes('sofort') || lower.includes('asap')
  let timing: ParsedNeedCriteria['timing'] | undefined
  let timingConfidence = 0.20
  if (soonMatch) {
    timing = { earliestMoveIn: '2025-07-01', latestMoveIn: '2025-10-01', flexibleTiming: false }
    timingConfidence = 0.85
  } else if (yearMatch) {
    timing = { earliestMoveIn: `${yearMatch[1]}-01-01`, latestMoveIn: `${yearMatch[1]}-12-31`, flexibleTiming: lower.includes('flexibel') }
    timingConfidence = 0.75
  } else if (quarterMatches.length > 0) {
    // Use first Q as earliest, last Q as latest (6-month window minimum)
    const qStartMonth = [0, 1, 4, 7, 10] // [unused, Q1, Q2, Q3, Q4]
    const first = quarterMatches[0]
    const last = quarterMatches[quarterMatches.length - 1]
    const yr1 = 2000 + parseInt(first[2])
    const yr2 = 2000 + parseInt(last[2])
    const q1 = parseInt(first[1])
    const q2 = parseInt(last[1])
    const startM = String(qStartMonth[q1]).padStart(2, '0')
    const endM = String(Math.min(12, qStartMonth[q2] + 2)).padStart(2, '0')
    timing = {
      earliestMoveIn: `${yr1}-${startM}-01`,
      latestMoveIn: `${yr2}-${endM}-${endM === '12' ? '31' : '28'}`,
      flexibleTiming: true,
    }
    timingConfidence = 0.70
  }

  // Must-haves
  const mustHaveCriteria: string[] = []
  if (lower.includes('öv') || lower.includes('bahnhof') || lower.includes('tram') || lower.includes('erschlossen')) mustHaveCriteria.push('Gute ÖV-Anbindung')
  if (lower.includes('parking') || lower.includes('parkplatz') || lower.includes('tiefgarage') || lower.includes('stellplatz')) mustHaveCriteria.push('Parkplätze vorhanden')
  if (lower.includes('klimaanlage') || lower.includes('klima')) mustHaveCriteria.push('Klimaanlage')
  if (lower.includes('server') || lower.includes('rechenzentr')) mustHaveCriteria.push('Serverraum / IT-Infrastruktur')
  if (lower.includes('barrierefrei')) mustHaveCriteria.push('Barrierefreiheit')
  if (lower.includes('küche') || lower.includes('kantine')) mustHaveCriteria.push('Kantine / Küche')
  if (lower.includes('laderampe') || lower.includes('rampe') || lower.includes('verladetor')) mustHaveCriteria.push('Laderampe')
  if (lower.includes('schaufenster') || lower.includes('shopfenster') || lower.includes('vitrine')) mustHaveCriteria.push('Schaufenster')
  if (lower.includes('verpflegung') || lower.includes('restaurant') || lower.includes('lunch') || lower.includes('mittagessen') || lower.includes('takeaway')) mustHaveCriteria.push('Verpflegungsmöglichkeiten')
  if (lower.includes('startup') || lower.includes('start-up') || lower.includes('community') || lower.includes('coworking') || lower.includes('junge firmen') || lower.includes('jungunternehm')) mustHaveCriteria.push('Startup-Community / innovative Nachbarn')
  if (lower.includes('wachsen') || lower.includes('expansion') || lower.includes('erweiterungsoption') || lower.includes('wachstum') || lower.includes('wachstumsoption') || lower.includes('flexibilität zum wachsen')) mustHaveCriteria.push('Erweiterungsoption vorhanden')
  if (lower.includes('werkstatt') || lower.includes('atelier') || lower.includes('reparatur')) mustHaveCriteria.push('Werkstatt / Atelier')

  // Structured requirement fields
  const requireGroundFloor = lower.includes('erdgeschoss') || lower.includes('parterre')
    || lower.includes('schaufenster') || lower.includes('ladenlokal') || undefined as boolean | undefined
  const requireAirConditioning = lower.includes('klimaanlage') || lower.includes('klimatisierung')
    || lower.includes('klima') || lower.includes('air conditioning')
    || lower.includes('kühlung') || undefined as boolean | undefined
  const requireLoadingDock = lower.includes('laderampe') || lower.includes('verladerampe')
    || lower.includes('ladetor') || lower.includes('rampe') || undefined as boolean | undefined
  const requireBarrierFree = lower.includes('barrierefrei') || lower.includes('rollstuhl')
    || lower.includes('iv-gerecht') || lower.includes('behindertengerecht') || undefined as boolean | undefined

  const ceilingMatch = input.match(/(\d+(?:[.,]\d+)?)\s*m(?:eter)?\s*(?:deckenhöhe|hallenhöhe|lichte\s+höhe)/i)
    ?? input.match(/deckenhöhe\s+(?:mind\.?\s*)?(\d+(?:[.,]\d+)?)\s*m/i)
  const minCeilingHeightM = ceilingMatch ? parseFloat(ceilingMatch[1].replace(',', '.')) : undefined

  // Parking: "3-5 Parkplätze" → extract lower bound (minimum); "5 Parkplätze" → 5
  const parkingRangeMatch = input.match(/(\d+)\s*[-–]\s*\d+\s*(?:parkplätze?|stellplätze?|pp\b)/i)
  const parkingSingleMatch = input.match(/(?:mind(?:estens)?\.?\s+)?(\d+)\s*(?:parkplätze?|stellplätze?|pp\b)/i)
  const requiredParkingMin = parkingRangeMatch ? parseInt(parkingRangeMatch[1])
    : parkingSingleMatch ? parseInt(parkingSingleMatch[1]) : undefined

  const fitOutStr: 'BASIC' | 'FULL' | 'PREMIUM' | undefined =
    lower.includes('schlüsselfertig') || lower.includes('premium') || lower.includes('hochwertig')
      || lower.includes('top ausgebaut') || lower.includes('top-ausgebaut') ? 'PREMIUM'
    : lower.includes('vollausbau') || lower.includes('vollständig ausgebaut') || lower.includes('ausgebaut')
      || lower.includes('ready to use') || lower.includes('reddy to use') || lower.includes('bezugsfertig') ? 'FULL'
    : lower.includes('basisausbau') || lower.includes('rohbau') || lower.includes('einfach') ? 'BASIC'
    : undefined

  // Search radius: "innerhalb von 30 km", "30km Umkreis", "im Umkreis von 50 km", "radius 20km"
  const radiusMatch =
    input.match(/(?:innerhalb\s+(?:von\s+)?|im\s+umkreis\s+(?:von\s+)?|radius\s+(?:von\s+)?)(\d+)\s*km/i) ??
    input.match(/(\d+)\s*km\s*(?:umkreis|radius|entfernung)/i) ??
    input.match(/(\d+)\s*km/i)
  const searchRadius = radiusMatch ? Math.min(100, Math.max(1, parseInt(radiusMatch[1]))) : undefined

  // Contract duration: "7-jähriger Vertrag", "Laufzeit 7 Jahre", standalone "7 Jahre" at sentence start
  // Exclude "in X Jahren", "X Jahre im Geschäft", "X Jahre Erfahrung" etc.
  const contractMatch = input.match(/(\d+)[- ]?j[aä]hrige?(?:r)?\s+(?:vertrag|mietvertrag|laufzeit)/i)
    ?? input.match(/(?:vertrag|laufzeit|mietdauer).{0,20}(\d+)\s*jahre?/i)
    ?? input.match(/\b(\d+)\s*jahre?\b(?!\s*(?:erfahrung|planung|rendite|im\b|alt\b|alten|altes|jung))/i)
  const minContractDurationMonths = contractMatch ? parseInt(contractMatch[1]) * 12 : undefined

  // Soft
  const prestigeImportance: 'LOW' | 'MEDIUM' | 'HIGH' | undefined =
    lower.includes('prestige') || lower.includes('representativ') || lower.includes('repräsentativ')
      || lower.includes('topadresse') || lower.includes('top adresse') || lower.includes('innerstädtisch')
      || lower.includes('topaddresse') ? 'HIGH'
    : lower.includes('standard') ? 'LOW'
    : undefined
  const parkingNeed = lower.includes('parking') || lower.includes('parkplatz')
  const visibilityNeed: 'HIGH' | undefined = lower.includes('sichtbar') || lower.includes('passanten') ? 'HIGH' : undefined
  const footfallNeed: 'HIGH' | undefined = lower.includes('frequenz') || lower.includes('laufkundschaft') ? 'HIGH' : undefined

  // Missing fields
  const missingFields: string[] = []
  if (!assetType) missingFields.push('Nutzungstyp')
  if (!areaRange) missingFields.push('Flächenbedarf')
  if (preferredLocations.length === 0) missingFields.push('Standort')
  if (!budgetRange) missingFields.push('Budget')
  if (!timing) missingFields.push('Verfügbarkeitstermin')

  // Assumptions
  const assumptions: string[] = []
  if (areaRange && areaSingleMatch && !areaRangeMatch) {
    assumptions.push(`Flächenrange aus Einzelangabe (${areaSingleMatch[1]} m²) geschätzt — bitte prüfen`)
  }
  if (budgetRange && !budgetPerSqmMatch && budgetMaxMatch) {
    assumptions.push('Budget als Pauschalangabe interpretiert — Angabe pro m² unklar')
  }
  if (!assetType) {
    assumptions.push('Nutzungstyp konnte nicht eindeutig erkannt werden')
  }

  // Confidence by field
  const confidenceByField: Record<string, number> = {
    assetType: assetType ? 0.92 : 0.20,
    areaRange: areaConfidence,
    preferredLocations: locationConfidence,
    budgetRange: budgetConfidence,
    timing: timingConfidence,
    mustHaveCriteria: mustHaveCriteria.length > 0 ? 0.85 : 0.10,
    searchRadius: searchRadius ? 0.90 : 0.20,
    prestigeImportance: prestigeImportance ? 0.80 : 0.20,
    parkingNeed: parkingNeed ? 0.90 : 0.30,
  }

  // Follow-up questions
  const followUpQuestionCandidates: FollowUpQuestion[] = []

  if (!assetType) {
    followUpQuestionCandidates.push({
      id: 'fq-asset-type',
      questionText: 'Welchen Nutzungstyp suchen Sie?',
      targetField: 'assetType',
      reason: 'Der Nutzungstyp konnte nicht eindeutig erkannt werden.',
      suggestedAnswerOptions: ['Büro', 'Logistik / Lager', 'Retail', 'Produktion', 'Gastro / F&B'],
      importance: 'required',
    })
  }
  if (preferredLocations.length === 0) {
    followUpQuestionCandidates.push({
      id: 'fq-location',
      questionText: 'In welcher Region oder Stadt suchen Sie?',
      targetField: 'preferredLocations',
      reason: 'Kein konkreter Standort angegeben.',
      suggestedAnswerOptions: ['Zürich', 'Basel', 'Bern', 'Zug', 'Luzern', 'Genf'],
      importance: 'required',
    })
  }
  if (!timing) {
    followUpQuestionCandidates.push({
      id: 'fq-timing',
      questionText: 'Ab wann benötigen Sie die Fläche?',
      targetField: 'timing',
      reason: 'Kein Verfügbarkeitsdatum erkannt.',
      suggestedAnswerOptions: ['Sofort', 'In 3 Monaten', 'In 6 Monaten', 'In 12 Monaten', 'Flexibel'],
      importance: 'recommended',
    })
  }
  if (!budgetRange) {
    followUpQuestionCandidates.push({
      id: 'fq-budget',
      questionText: 'Was ist Ihr Maximalbudget pro m² und Jahr?',
      targetField: 'budgetRange',
      reason: 'Kein Budget erkannt.',
      suggestedAnswerOptions: ['< CHF 300/m²/J', 'CHF 300–420/m²/J', 'CHF 420–540/m²/J', '> CHF 540/m²/J', 'Flexibel'],
      importance: 'recommended',
    })
  }
  followUpQuestionCandidates.push({
    id: 'fq-parking',
    questionText: 'Benötigen Sie Parkplätze vor Ort?',
    targetField: 'parkingNeed',
    reason: 'Angabe zu Parkplatzbedarf verbessert die Matchqualität.',
    suggestedAnswerOptions: ['Ja, zwingend', 'Ja, wenn möglich', 'Nein'],
    importance: 'optional',
  })

  // Special notes: apartment wish (UC-A type) + lease options (UC-C type)
  const notesParts: string[] = []
  const apartmentMatch = input.match(/(\d[.,]\d)\s*zimmer[- ]?wohn/i)
    ?? (lower.includes('zimmer') && lower.includes('wohnung') ? [''] : null)
  if (apartmentMatch) {
    const sizeHint = apartmentMatch[1] ? `${apartmentMatch[1]}-Zi-Wohnung` : 'Wohnung'
    const rentMatch = input.match(/(\d['.\s]?\d{3})[.,\-\s]*(?:inkl|inkl\.)/i)
      ?? input.match(/(\d{4})[.,\-]\s*(?:chf|fr)?/i)
    const rentHint = rentMatch ? ` max. CHF ${rentMatch[1].replace(/['\s]/g, "'")}` : ''
    notesParts.push(`Wunsch: ${sizeHint} im Haus oder in der Nähe${rentHint} inkl. NK`)
  }
  const leaseOptionMatch = input.match(/(\d+)\s*[*×x]\s*(\d+)\s*jahre?\s*(?:echte\s*)?optione?n?/i)
  if (leaseOptionMatch) {
    notesParts.push(`Mietoption: ${leaseOptionMatch[1]}×${leaseOptionMatch[2]} Jahre echte Optionen gefordert`)
  }
  const notes = notesParts.length > 0 ? notesParts.join(' | ') : undefined

  // Semantic signal flags used for weight boosting
  const hasExpansionSignal = lower.includes('wachsen') || lower.includes('wachstum') || lower.includes('expansion') || lower.includes('erweiterung')
  const hasCommunitySignal = lower.includes('startup') || lower.includes('community') || lower.includes('coworking') || lower.includes('junge firmen')
  const hasPrestigeSignal = prestigeImportance === 'HIGH' || lower.includes('repräsentativ') || lower.includes('charakter') || lower.includes('beeindrucken')
  const hasFlexSignal = lower.includes('flexibel') || lower.includes('wachsen') || hasCommunitySignal

  // Suggested weights
  const suggestedWeights: Record<string, number> = {
    area: 0.18,
    location: preferredLocations.length > 0 ? 0.25 : 0.20,
    budget: budgetRange ? 0.20 : 0.16,
    timing: timing ? 0.14 : 0.10,
    prestige: hasPrestigeSignal ? 0.10 : 0.04,
    accessibility: mustHaveCriteria.includes('Gute ÖV-Anbindung') ? 0.08 : 0.04,
    expansionPotential: hasExpansionSignal ? 0.08 : 0.03,
    flexibility: hasFlexSignal ? 0.08 : 0.03,
    talentAccess: hasCommunitySignal ? 0.06 : 0.03,
  }

  const rawSummary = `Bedarf analysiert: ${assetType ?? 'Typ unbekannt'} · ${preferredLocations.join(', ') || 'Standort unklar'} · ${areaRange ? `${areaRange.min}–${areaRange.max} m²` : 'Fläche unklar'} · ${budgetRange ? `max. CHF ${budgetRange.maxPerSqm}/m²` : 'Budget unklar'}`

  return {
    extractedCriteria: {
      assetType,
      areaRange,
      preferredLocations,
      budgetRange,
      timing,
      mustHaveCriteria,
      infrastructureRequirements: [],
      accessibilityRequirements: [],
      prestigeImportance: hasPrestigeSignal ? 'HIGH' : prestigeImportance,
      flexibilityNeed: hasFlexSignal ? 'HIGH' : 'MEDIUM',
      expansionPotential: hasExpansionSignal,
      parkingNeed,
      visibilityNeed,
      footfallNeed,
      requireGroundFloor: requireGroundFloor || undefined,
      requireAirConditioning: requireAirConditioning || undefined,
      requireLoadingDock: requireLoadingDock || undefined,
      requireBarrierFree: requireBarrierFree || undefined,
      requiredParkingMin,
      requiredFitOut: fitOutStr,
      minCeilingHeightM,
      minContractDurationMonths,
      searchRadius,
      notes,
    },
    confidenceByField,
    missingFields,
    assumptions,
    suggestedWeights,
    followUpQuestionCandidates,
    rawSummary,
    promptVersion: 'mock-v1.0',
    schemaVersion: '1.0.0',
  }
}
