/**
 * Backend AI Service — PowerOn Proxy
 *
 * Routes all LLM calls through the PowerOn backend. The LLM provider API key
 * is stored ONLY server-side and never reaches the browser bundle.
 *
 * ┌─────────────────────────────────────────────────────────────────────────┐
 * │  PowerOn Backend Contract                                               │
 * │                                                                         │
 * │  Endpoint:  POST /api/ai/chat/completions                               │
 * │  Headers:   Content-Type: application/json                              │
 * │             (session auth cookie handled by backend — no API key here)  │
 * │                                                                         │
 * │  Request body:                                                          │
 * │    {                                                                    │
 * │      messages: { role: 'system' | 'user'; content: string }[]         │
 * │    }                                                                    │
 * │                                                                         │
 * │  Response (OpenAI-compatible):                                          │
 * │    {                                                                    │
 * │      choices: [{ message: { content: string } }]                       │
 * │    }                                                                    │
 * │                                                                         │
 * │  The backend adds:                                                      │
 * │    - Authorization: Bearer <OPENROUTER_API_KEY>  (server-side env var) │
 * │    - Model selection / routing                                          │
 * │    - Rate limiting & audit logging                                      │
 * └─────────────────────────────────────────────────────────────────────────┘
 *
 * Dev setup — add to vite.config.ts:
 *   server: { proxy: { '/api': process.env.AI_BACKEND_URL ?? 'http://localhost:3001' } }
 *
 * Every method follows this contract:
 *   1. HTTP error        → error log + MockAIService fallback
 *   2. JSON parse fail   → warn + MockAIService fallback
 *   3. Zod schema fail   → warn + MockAIService fallback
 *   4. Success           → AI response, source: 'ai', validationPassed: true
 */
import type { CreateNeedInput } from '../../../domain/need'
import type { ParseNeedResult, ParsedNeedCriteria, FollowUpQuestion } from '../../../domain/needBuilder'
import type { UnifiedMatchResult } from '../../../domain/unifiedResult'
import type { AssetType } from '../../../domain/enums'
import type {
  IAIService,
  AIResponse,
  AIProvenance,
  DecisionBrief,
  ComparisonSummary,
  CriteriaExtractionResult,
  OfferEmailPayload,
  MatchExplanationInput,
  MatchExplanation,
  TradeOffInput,
  TradeOffSummary,
  DataQualityInput,
  DataQualitySummary,
  MarketSignalClassification,
  FitOutAdviceInput,
  FitOutAdvice,
  PreMarketRentInput,
  PreMarketRentRecommendation,
} from '../IAIService'
import { ServiceErrorCode } from '../../types'
import { AppError } from '../../errors'
import { aiTraceStore, provenanceToStatus } from '../tracing'
import type { AITraceErrorType, AITraceValidationStatus } from '../tracing'
import {
  NeedParsingResponseSchema,
  FollowUpQuestionsResponseSchema,
  TradeOffSummaryResponseSchema,
  CompareSummaryResponseSchema,
  DecisionBriefResponseSchema,
  DataQualitySummaryResponseSchema,
  MarketSignalClassificationResponseSchema,
  OfferEmailResponseSchema,
  validateAIResponse,
} from '../schemas'
import { buildNeedParsingPrompt } from '../prompts/needParsingPrompt'
import { buildFollowUpQuestionsPrompt } from '../prompts/followUpQuestionsPrompt'
import { buildMatchExplanationPrompt } from '../prompts/matchExplanationPrompt'
import { buildTradeOffPrompt } from '../prompts/tradeOffPrompt'
import { buildCompareSummaryPrompt } from '../prompts/compareSummaryPrompt'
import { buildDecisionBriefPrompt } from '../prompts/decisionBriefPrompt'
import { buildDataQualityPrompt } from '../prompts/dataQualityPrompt'
import { buildMarketSignalPrompt } from '../prompts/marketSignalPrompt'
import { MockAIService } from '../mock/MockAIService'
import { getCityIntelligence, getMarketRent } from '../../../lib/locationIntelligence'

// ── Config ────────────────────────────────────────────────────────────────────

/** Relative URL — resolved by Vite proxy in dev, by the same-origin backend in prod. */
const API_BASE = '/api/ai'

/**
 * Placeholder recorded in traces. The actual model is backend-controlled;
 * PowerOn may return it in a response extension field in future.
 */
const BACKEND_MODEL_PLACEHOLDER = 'backend-controlled'

const PROMPT_VERSION = 'v1.1'
const SCHEMA_VERSION = 'v1.0'

// ── Provenance ────────────────────────────────────────────────────────────────

function makeProvenance(
  source: AIProvenance['source'],
  fallbackUsed: boolean,
  validationPassed: boolean,
  extras: { fallbackReason?: string } = {},
): AIProvenance {
  return {
    provider: 'backend',
    model: BACKEND_MODEL_PLACEHOLDER,
    generatedAt: new Date().toISOString(),
    promptVersion: PROMPT_VERSION,
    schemaVersion: SCHEMA_VERSION,
    source,
    fallbackUsed,
    validationPassed,
    traceId: crypto.randomUUID(),
    fallbackReason: extras.fallbackReason,
  }
}

// ── HTTP helper ───────────────────────────────────────────────────────────────

async function chat(system: string, user: string): Promise<string> {
  const res = await fetch(`${API_BASE}/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      // No Authorization header — the API key lives server-side only.
    },
    body: JSON.stringify({
      messages: [
        { role: 'system', content: system },
        { role: 'user',   content: user   },
      ],
    }),
  })
  if (!res.ok) {
    const body = await res.text()
    throw new AppError({
      code:    ServiceErrorCode.AI_GENERATION_FAILED,
      // Truncate to avoid leaking full backend error detail to the console.
      message: `Backend AI error ${res.status}: ${body.slice(0, 200)}`,
    })
  }
  const json = await res.json() as { choices: Array<{ message: { content: string } }> }
  return json.choices[0]?.message?.content ?? ''
}

// ── JSON extraction ───────────────────────────────────────────────────────────

function extractJSON<T>(raw: string): T | null {
  const fenced    = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/)
  const candidate = fenced ? fenced[1] : raw.match(/([\[{][\s\S]*[\]}])/)?.[1] ?? raw
  try {
    return JSON.parse(candidate) as T
  } catch {
    return null
  }
}

// ── ParseNeed helpers ─────────────────────────────────────────────────────────

type RawNeedParseAI = {
  assetType?: string | null
  areaRange?: { min: number; max: number } | null
  preferredLocations?: string[]
  budgetRange?: { maxPerSqm: number; currency: string } | null
  timing?: { earliestMoveIn: string; latestMoveIn?: string; flexibleTiming: boolean } | null
  mustHaveCriteria?: string[]
  missingFields?: string[]
  assumptions?: string[]
}

function followUpForField(field: string): string {
  const MAP: Record<string, string> = {
    assetType:          'Welchen Nutzungstyp suchen Sie (Büro, Retail, Logistik, Produktion)?',
    areaRange:          'Welche Fläche benötigen Sie (min–max in m²)?',
    preferredLocations: 'In welchen Städten oder Regionen suchen Sie?',
    budgetRange:        'Was ist Ihr maximales Budget pro m² und Jahr?',
    timing:             'Wann möchten Sie spätestens einziehen?',
    mustHaveCriteria:   'Haben Sie zwingende Anforderungen (ÖV-Anbindung, Parkplätze, Laderampe)?',
  }
  return MAP[field] ?? `Können Sie "${field}" präzisieren?`
}

function defaultSuggestedWeights(): Record<string, number> {
  return {
    area: 0.25, location: 0.20, budget: 0.20, timing: 0.15,
    prestige: 0.05, accessibility: 0.05, expansionPotential: 0.02,
    flexibility: 0.02, visibility: 0.02, footfall: 0.01, talentAccess: 0.01,
    esg: 0.01, taxEnvironment: 0.01,
  }
}

// ── Fallback wrapper ──────────────────────────────────────────────────────────

type FallbackFn<T> = () => Promise<AIResponse<T>>

async function withFallback<T>(
  label: string,
  fn: () => Promise<AIResponse<T>>,
  fallback: FallbackFn<T>,
  inputSizeChars?: number,
): Promise<AIResponse<T>> {
  const startMs = Date.now()
  const callId  = crypto.randomUUID()

  try {
    const result    = await fn()
    const latencyMs = Date.now() - startMs
    const prov      = result.provenance
    const provenance: AIProvenance = {
      ...prov,
      traceId: callId,
      latencyMs,
      schemaVersion: SCHEMA_VERSION,
    }
    aiTraceStore.add({
      id: callId,
      method: label,
      provider: prov.provider,
      model: prov.model,
      promptVersion: prov.promptVersion,
      latencyMs,
      fallbackUsed: prov.fallbackUsed,
      validationPassed: prov.validationPassed,
      responseValidationStatus: provenanceToStatus(prov.fallbackUsed, prov.source, prov.fallbackReason),
      fallbackReason: prov.fallbackReason,
      source: prov.source,
      createdAt: prov.generatedAt,
      inputSizeChars,
    })
    return { ...result, provenance }
  } catch (err) {
    console.error(`[BackendAIService] ${label} failed:`, err)
    const result    = await fallback()
    const latencyMs = Date.now() - startMs
    const errorType: AITraceErrorType =
      err instanceof AppError && err.code === ServiceErrorCode.AI_GENERATION_FAILED
        ? 'api_error'
        : err instanceof TypeError
          ? 'network'
          : 'unknown'
    const responseValidationStatus: AITraceValidationStatus =
      err instanceof AppError && err.code === ServiceErrorCode.AI_GENERATION_FAILED
        ? 'api_error'
        : 'network_error'
    const fallbackReason = `${errorType}: ${err instanceof Error ? err.message.slice(0, 100) : 'unknown error'}`
    const provenance: AIProvenance = {
      ...result.provenance,
      fallbackUsed: true,
      traceId: callId,
      fallbackReason,
      schemaVersion: SCHEMA_VERSION,
      latencyMs,
    }
    aiTraceStore.add({
      id: callId,
      method: label,
      provider: 'backend',
      model: BACKEND_MODEL_PLACEHOLDER,
      promptVersion: PROMPT_VERSION,
      latencyMs,
      fallbackUsed: true,
      validationPassed: false,
      responseValidationStatus,
      errorType,
      fallbackReason,
      source: 'mock',
      createdAt: new Date().toISOString(),
      inputSizeChars,
    })
    return { ...result, provenance }
  }
}

// ── Service ───────────────────────────────────────────────────────────────────

export const BackendAIService: IAIService = {

  // ── parseNeed ───────────────────────────────────────────────────────────────
  parseNeed(input: string): Promise<AIResponse<ParseNeedResult>> {
    return withFallback('parseNeed', async () => {
      const { system, user } = buildNeedParsingPrompt({ userInput: input })
      const raw  = await chat(system, user)
      const json = extractJSON<RawNeedParseAI>(raw)
      const ai   = json ? validateAIResponse(NeedParsingResponseSchema, json, 'parseNeed') : null

      if (!ai) {
        console.warn('[BackendAIService] parseNeed: invalid response — using mock fallback')
        const fb = await MockAIService.parseNeed(input)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }

      const extractedCriteria: ParseNeedResult['extractedCriteria'] = {
        assetType:          (ai.assetType ?? undefined) as AssetType | undefined,
        areaRange:          ai.areaRange ?? undefined,
        preferredLocations: ai.preferredLocations,
        budgetRange:        ai.budgetRange ?? undefined,
        timing: ai.timing
          ? { ...ai.timing, earliestMoveIn: ai.timing.earliestMoveIn ?? '', flexibleTiming: ai.timing.flexibleTiming ?? false }
          : undefined,
        mustHaveCriteria: ai.mustHaveCriteria,
      }
      const missingFields      = ai.missingFields ?? []
      const confidenceByField: Record<string, number> = {}
      Object.keys(extractedCriteria).forEach(k => {
        confidenceByField[k] = extractedCriteria[k as keyof typeof extractedCriteria] != null ? 0.85 : 0
      })
      missingFields.forEach(f => { confidenceByField[f] = 0 })
      const followUpQuestionCandidates: FollowUpQuestion[] = missingFields.map((field, i) => ({
        id:           `fq-be-${i}`,
        questionText: followUpForField(field),
        targetField:  field,
        reason:       `Feld "${field}" nicht im Text erkannt`,
        importance:   'recommended' as const,
      }))
      return {
        data: {
          extractedCriteria,
          confidenceByField,
          missingFields,
          assumptions:               ai.assumptions ?? [],
          suggestedWeights:          defaultSuggestedWeights(),
          followUpQuestionCandidates,
          rawSummary:                raw.substring(0, 500),
          promptVersion:             PROMPT_VERSION,
          schemaVersion:             SCHEMA_VERSION,
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.parseNeed(input), input.length)
  },

  // ── generateFollowUpQuestions ───────────────────────────────────────────────
  generateFollowUpQuestions(criteria: ParsedNeedCriteria): Promise<AIResponse<FollowUpQuestion[]>> {
    return withFallback('generateFollowUpQuestions', async () => {
      const missingFields = [
        ...(!criteria.assetType ? ['assetType'] : []),
        ...(!criteria.areaRange || (criteria.areaRange.min <= 0 && criteria.areaRange.max <= 0) ? ['areaRange'] : []),
        ...(!criteria.preferredLocations?.length ? ['preferredLocations'] : []),
        ...(!criteria.budgetRange ? ['budgetRange'] : []),
        ...(!criteria.timing ? ['timing'] : []),
        ...(!criteria.mustHaveCriteria?.length ? ['mustHaveCriteria'] : []),
      ]
      const { system, user } = buildFollowUpQuestionsPrompt({ criteria, missingFields })
      const raw  = await chat(system, user)
      const json = extractJSON<unknown[]>(raw)
      const ai   = json ? validateAIResponse(FollowUpQuestionsResponseSchema, json, 'generateFollowUpQuestions') : null

      if (!ai?.length) {
        console.warn('[BackendAIService] generateFollowUpQuestions: invalid response — using mock fallback')
        const fb = await MockAIService.generateFollowUpQuestions(criteria)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: ai.map((q, i) => ({
          id:                     `fq-be-${i}`,
          questionText:           q.questionText,
          targetField:            q.targetField,
          reason:                 q.reason ?? 'AI-generiert',
          suggestedAnswerOptions: q.suggestedAnswerOptions,
          importance:             (q.importance ?? 'recommended') as FollowUpQuestion['importance'],
        })),
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateFollowUpQuestions(criteria))
  },

  // ── generateMatchExplanation ────────────────────────────────────────────────
  generateMatchExplanation(input: MatchExplanationInput): Promise<AIResponse<MatchExplanation>> {
    return withFallback('generateMatchExplanation', async () => {
      const { system, user } = buildMatchExplanationPrompt(input)
      const raw     = await chat(system, user)
      const summary = raw.trim()

      if (!summary) {
        console.warn('[BackendAIService] generateMatchExplanation: empty response — using mock fallback')
        const fb = await MockAIService.generateMatchExplanation(input)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: 'empty_response' }) }
      }
      const scoreLabel = input.matchScore >= 78 ? 'Starkes' : input.matchScore >= 52 ? 'Gutes' : 'Schwaches'
      return {
        data: {
          headline:   `${scoreLabel} Match — ${input.propertyTitle} (${input.matchScore}/100)`,
          summary,
          keyReasons: [
            ...input.positiveFactors.slice(0, 2).map(f => `+ ${f.explanation}`),
            ...input.negativeFactors.slice(0, 1).map(f => `− ${f.explanation}`),
          ],
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateMatchExplanation(input))
  },

  // ── summarizeTradeOffs ──────────────────────────────────────────────────────
  summarizeTradeOffs(tradeoffs: TradeOffInput[]): Promise<AIResponse<TradeOffSummary>> {
    return withFallback('summarizeTradeOffs', async () => {
      const { system, user } = buildTradeOffPrompt(tradeoffs, 'Objekt')
      const raw  = await chat(system, user)
      const json = extractJSON<unknown>(raw)
      const ai   = json ? validateAIResponse(TradeOffSummaryResponseSchema, json, 'summarizeTradeOffs') : null

      if (!ai) {
        console.warn('[BackendAIService] summarizeTradeOffs: invalid response — using mock fallback')
        const fb = await MockAIService.summarizeTradeOffs(tradeoffs)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: {
          headline:    ai.headline,
          items:       ai.items.map(item => ({
            concern:    item.concern,
            severity:   item.severity,
            mitigation: item.mitigation,
          })),
          overallRisk: ai.overallRisk,
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.summarizeTradeOffs(tradeoffs))
  },

  // ── summarizeComparison ─────────────────────────────────────────────────────
  summarizeComparison(items: UnifiedMatchResult[]): Promise<AIResponse<ComparisonSummary>> {
    return withFallback('summarizeComparison', async () => {
      type ItemWithProp = UnifiedMatchResult & {
        property?: { title?: string; location?: { city?: string }; rentPricePerSqm?: number }
      }
      const properties = (items as ItemWithProp[])
        .filter(i => i.resultType !== 'FUTURE_AVAILABILITY')
        .map(i => ({
          title:           i.property?.title ?? `Match ${i.matchScore}`,
          matchScore:      i.matchScore,
          city:            i.property?.location?.city ?? '–',
          rentPerSqm:      i.property?.rentPricePerSqm ?? 0,
          positiveFactors: i.match.positiveFactors.slice(0, 2).map(f => f.explanation ?? f.criterion),
          negativeFactors: i.match.negativeFactors.slice(0, 2).map(f => f.explanation ?? f.criterion),
        }))
      const { system, user } = buildCompareSummaryPrompt({ properties })
      const raw  = await chat(system, user)
      const json = extractJSON<unknown>(raw)
      const ai   = json ? validateAIResponse(CompareSummaryResponseSchema, json, 'summarizeComparison') : null

      if (!ai) {
        console.warn('[BackendAIService] summarizeComparison: invalid response — using mock fallback')
        const fb = await MockAIService.summarizeComparison(items)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      const mock = await MockAIService.summarizeComparison(items)
      return {
        data: {
          ...mock.data,
          overallAssessment: ai.overallAssessment,
          recommendation:    ai.recommendation ?? mock.data.recommendation,
        },
        provenance: makeProvenance('hybrid', false, true),
      }
    }, () => MockAIService.summarizeComparison(items))
  },

  // ── generateDecisionBrief ───────────────────────────────────────────────────
  generateDecisionBrief(shortlistId: string): Promise<AIResponse<DecisionBrief>> {
    return withFallback('generateDecisionBrief', async () => {
      const { system, user } = buildDecisionBriefPrompt({ shortlistItems: [], needSummary: shortlistId })
      const raw  = await chat(system, user)
      const json = extractJSON<unknown>(raw)
      const ai   = json ? validateAIResponse(DecisionBriefResponseSchema, json, 'generateDecisionBrief') : null

      if (!ai) {
        console.warn('[BackendAIService] generateDecisionBrief: invalid response — using mock fallback')
        const fb = await MockAIService.generateDecisionBrief(shortlistId)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      const mock = await MockAIService.generateDecisionBrief(shortlistId)
      return {
        data: {
          ...mock.data,
          summary:  ai.summary,
          sections: ai.sections.map(s => ({ title: s.title, body: s.body })),
        },
        provenance: makeProvenance('hybrid', false, true),
      }
    }, () => MockAIService.generateDecisionBrief(shortlistId))
  },

  // ── generateDataQualitySummary ──────────────────────────────────────────────
  generateDataQualitySummary(propertyId: string, quality: DataQualityInput): Promise<AIResponse<DataQualitySummary>> {
    return withFallback('generateDataQualitySummary', async () => {
      const { system, user } = buildDataQualityPrompt(propertyId, quality)
      const raw  = await chat(system, user)
      const json = extractJSON<unknown>(raw)
      const ai   = json ? validateAIResponse(DataQualitySummaryResponseSchema, json, 'generateDataQualitySummary') : null

      if (!ai) {
        console.warn('[BackendAIService] generateDataQualitySummary: invalid response — using mock fallback')
        const fb = await MockAIService.generateDataQualitySummary(propertyId, quality)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: {
          overallAssessment:     ai.overallAssessment,
          missingCriticalFields: ai.missingCriticalFields ?? quality.missingCriticalFields,
          recommendation:        ai.recommendation,
          confidence:            ai.confidence,
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateDataQualitySummary(propertyId, quality))
  },

  // ── classifyMarketSignal ────────────────────────────────────────────────────
  classifyMarketSignal(signalText: string): Promise<AIResponse<MarketSignalClassification>> {
    return withFallback('classifyMarketSignal', async () => {
      const { system, user } = buildMarketSignalPrompt(signalText)
      const raw  = await chat(system, user)
      const json = extractJSON<unknown>(raw)
      const ai   = json
        ? validateAIResponse(MarketSignalClassificationResponseSchema, json, 'classifyMarketSignal')
        : null

      if (!ai) {
        console.warn('[BackendAIService] classifyMarketSignal: invalid response — using mock fallback')
        const fb = await MockAIService.classifyMarketSignal(signalText)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: {
          signalType:        ai.signalType,
          probability:       ai.probability,
          timeHorizonMonths: ai.timeHorizonMonths ?? null,
          areaSqmEstimate:   ai.areaSqmEstimate ?? null,
          credibility:       ai.credibility,
          reasoning:         ai.reasoning,
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.classifyMarketSignal(signalText), signalText.length)
  },

  // ── generateOfferEmail ──────────────────────────────────────────────────────
  generateOfferEmail(payload: OfferEmailPayload): Promise<AIResponse<{ subject: string; body: string }>> {
    return withFallback('generateOfferEmail', async () => {
      const propertyList = payload.properties
        .map((p, i) => `• ${p} (Match-Score: ${payload.matchScores[i]}%)`)
        .join('\n')
      const system = `Du bist Immobilienmakler bei Wincasa AG. Erstelle eine professionelle, knappe Angebotsmail auf Deutsch. Antworte als JSON: { "subject": "...", "body": "..." }`
      const user   = `Suchanfrage: "${payload.needTitle}"\n\nObjekte:\n${propertyList}\n\nErstelle eine professionelle Angebotsmail.`
      const raw    = await chat(system, user)
      const json   = extractJSON<unknown>(raw)
      const ai     = json ? validateAIResponse(OfferEmailResponseSchema, json, 'generateOfferEmail') : null

      if (!ai) {
        console.warn('[BackendAIService] generateOfferEmail: invalid response — using mock fallback')
        const fb = await MockAIService.generateOfferEmail(payload)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data:      { subject: ai.subject, body: ai.body },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateOfferEmail(payload))
  },

  // ── generateFitOutAdvice ────────────────────────────────────────────────────
  generateFitOutAdvice(input: FitOutAdviceInput): Promise<AIResponse<FitOutAdvice>> {
    return withFallback('generateFitOutAdvice', async () => {
      const FIT_LABELS: Record<string, string> = { SHELL: 'Rohbau', BASIC: 'Basisausbau', FULL: 'Vollausbau', PREMIUM: 'Premiumausbau' }
      const system = `Du bist Schweizer Gewerbeimmobilien-Experte. Bewerte die Ausbausituation und empfiehl die beste Verhandlungsoption.
Verfügbare Optionen: MIETERAUSBAU (Mieter zahlt alles), BKZ (Vermieter zahlt Einmalpauschale), MAB_AMORTISATION (MAB über Miete amortisiert).
Antworte als JSON:
{
  "recommendation": "MIETERAUSBAU" | "BKZ" | "MAB_AMORTISATION",
  "headline": "kurze Empfehlung (max 80 Zeichen)",
  "explanation": "2-3 Sätze Begründung auf Deutsch",
  "negotiationTip": "konkreter Verhandlungstipp auf Deutsch",
  "estimatedNetInvestment": "CHF-Betrag als String"
}`
      const user = `Übergabezustand: ${FIT_LABELS[input.fitOut] ?? input.fitOut}
Fläche: ${input.areaSqm} m²
MAB des Vermieters: CHF ${input.mabPerSqm}/m²
Monatliche Miete: CHF ${input.monthlyRentPerSqm}/m²${input.tenantBudgetPerSqm ? `\nEigenes Ausbaubudget: CHF ${input.tenantBudgetPerSqm}/m²` : ''}${input.requiredFitOut ? `\nGewünschter Zustand: ${FIT_LABELS[input.requiredFitOut] ?? input.requiredFitOut}` : ''}

Bitte analysiere die Situation und empfiehl die beste Option für den Mieter.`

      const raw  = await chat(system, user)
      const json = extractJSON<FitOutAdvice>(raw)

      if (!json || !json.recommendation || !json.headline) {
        console.warn('[BackendAIService] generateFitOutAdvice: invalid response — using mock fallback')
        const fb = await MockAIService.generateFitOutAdvice(input)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: {
          recommendation:         json.recommendation,
          headline:               json.headline,
          explanation:            json.explanation ?? '',
          negotiationTip:         json.negotiationTip ?? '',
          estimatedNetInvestment: json.estimatedNetInvestment ?? '–',
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateFitOutAdvice(input))
  },

  // ── recommendPreMarketRent ──────────────────────────────────────────────────
  recommendPreMarketRent(input: PreMarketRentInput): Promise<AIResponse<PreMarketRentRecommendation>> {
    return withFallback('recommendPreMarketRent', async () => {
      const intel = getCityIntelligence(input.city)
      const comp = getMarketRent(input.city, input.assetType)
      const system = `Du bist Schweizer Gewerbeimmobilien-Marktanalyst. Empfiehl einen Pre-Market-Mietpreis (CHF/m²/Jahr) auf Basis regionaler Vergleichsmieten, Angebot (Leerstand) und Nachfrage. Antworte als JSON:
{
  "recommendedPerSqm": number,
  "rangeMinPerSqm": number,
  "rangeMaxPerSqm": number,
  "verdict": "UNDERPRICED" | "FAIR" | "AMBITIOUS",
  "deltaVsCurrentPct": number,
  "drivers": ["kurze Treiber auf Deutsch"],
  "rationale": "2-3 Sätze Begründung auf Deutsch",
  "confidence": "LOW" | "MEDIUM" | "HIGH"
}`
      const user = `Stadt: ${input.city}
Nutzung: ${input.assetType}
Fläche: ${input.areaSqm} m²
Heutiger Preis: CHF ${input.currentRentPerSqm}/m²
Vergleichsmiete (Median): ${comp ?? 'unbekannt'}
Leerstand: ${intel?.vacancyRatePct ?? '?'}%
Nachfrage: ${intel?.demandStrength ?? '?'}
Miettrend 12M: ${intel?.rentTrend12m ?? '?'}%
Ø Vermietungsdauer: ${intel?.avgDaysOnMarket ?? '?'} Tage`
      const raw = await chat(system, user)
      const json = extractJSON<PreMarketRentRecommendation>(raw)
      if (!json || typeof json.recommendedPerSqm !== 'number') {
        const fb = await MockAIService.recommendPreMarketRent(input)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return { data: json, provenance: makeProvenance('ai', false, true) }
    }, () => MockAIService.recommendPreMarketRent(input))
  },

  // ── Legacy: extractCriteria ─────────────────────────────────────────────────
  extractCriteria(input: string): Promise<AIResponse<CriteriaExtractionResult>> {
    return withFallback('extractCriteria', async () => {
      const { system, user } = buildNeedParsingPrompt({ userInput: input })
      const raw  = await chat(system, user)
      const json = extractJSON<RawNeedParseAI>(raw)
      const ai   = json ? validateAIResponse(NeedParsingResponseSchema, json, 'extractCriteria') : null

      if (!ai) {
        console.warn('[BackendAIService] extractCriteria: invalid response — using mock fallback')
        const fb = await MockAIService.extractCriteria(input)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data: {
          extractedCriteria: {
            assetType:          ai.assetType as AssetType | undefined ?? undefined,
            requiredArea:       ai.areaRange ?? undefined,
            preferredLocations: ai.preferredLocations ?? [],
            budgetRange:        ai.budgetRange ?? undefined,
          },
          confidence:        0.80,
          missingFields:     ai.missingFields ?? [],
          assumptions:       ai.assumptions ?? [],
          followUpQuestions: (ai.missingFields ?? []).map(followUpForField),
        },
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.extractCriteria(input))
  },

  // ── Legacy: generateFollowUp ────────────────────────────────────────────────
  generateFollowUp(partialNeed: Partial<CreateNeedInput>): Promise<AIResponse<string[]>> {
    return withFallback('generateFollowUp', async () => {
      const missingFields = [
        ...(!partialNeed.assetType                   ? ['assetType']          : []),
        ...(!partialNeed.preferredLocations?.length  ? ['preferredLocations'] : []),
        ...(!partialNeed.timing                      ? ['timing']             : []),
        ...(!partialNeed.budgetRange                 ? ['budgetRange']        : []),
      ]
      if (!missingFields.length) {
        return { data: [], provenance: makeProvenance('ai', false, true) }
      }
      const { system, user } = buildFollowUpQuestionsPrompt({
        criteria: partialNeed as ParsedNeedCriteria,
        missingFields,
      })
      const raw  = await chat(system, user)
      const json = extractJSON<unknown[]>(raw)
      const ai   = json ? validateAIResponse(FollowUpQuestionsResponseSchema, json, 'generateFollowUp') : null

      if (!ai?.length) {
        console.warn('[BackendAIService] generateFollowUp: invalid response — using mock fallback')
        const fb = await MockAIService.generateFollowUp(partialNeed)
        return { ...fb, provenance: makeProvenance('mock', true, false, { fallbackReason: json ? 'schema_validation' : 'json_parse' }) }
      }
      return {
        data:      ai.map(q => q.questionText).filter(Boolean),
        provenance: makeProvenance('ai', false, true),
      }
    }, () => MockAIService.generateFollowUp(partialNeed))
  },
}
