// Pure preview builder (Wertstrom P6, Baustein 4a). NO Supabase, NO writes —
// "PERSISTIERT NICHTS" is enforced structurally by this file never importing
// `@supabase/supabase-js` or `@/lib/supabase/*` at all. The route
// (app/api/wertstrom/import/preview) layers the DB-backed duplicate-hint
// lookup (internal/duplicates.ts) on top of this function's output —
// keeping the two concerns testable independently (this file's tests never
// need a database).

import { PARSER_VERSION, parseSimVsmJsonText } from './parser'
import { MAPPING_VERSION, classifyLink, classifyNode, findUnknownLinkClasses, findUnknownNodeClasses } from './mapping-registry'
import { computeSourceSignature } from './signature'
import { pairSvgFiles, groupRelatedFileNames } from './families'
import { resolveCurrentStreamIndex, mapStream, type MappedStream, type ScenarioRole } from './streams'
import { buildImportReport, type SimvsmImportReport } from './report'
import type { SimVsmParsedFile } from './types'

export interface PreviewClassCoverageRow {
  simvsmClass: string
  kind: 'node' | 'link'
  count: number
  supported: boolean
  targetLabelDe: string
  confidence: number
}

export interface PreviewStream {
  index: number
  name: string
  isMainFlag: boolean
  scenarioRole: ScenarioRole
  hasResultData: boolean
  /** SimVSM's own modificationTime, passed through as preview context (see
   * streams.ts module header, current-Wahl signal 3) — `undefined` when
   * absent/unparseable. Rendered German-formatted by the dialog, never
   * re-parsed here (this layer stays presentation-agnostic). */
  modificationTime?: string
  nodeCount: number
  mappedNodeCount: number
  connectionCount: number
  mappedConnectionCount: number
}

export interface PreviewFamily {
  fileName: string
  sourceSignature: string
  parseOk: boolean
  parseError?: string
  modelName?: string
  description?: string
  createdWithVersion?: string | null
  mainVersion?: string | null
  pairedSvgFileNames: string[]
  relatedFileNames: string[]
  streams: PreviewStream[]
  classCoverage: PreviewClassCoverageRow[]
  unknownClasses: string[]
  report: SimvsmImportReport
  /** Filled in by the route after a DB duplicate-check — absent here (pure
   * layer has no DB access). See app/api/wertstrom/import/preview/route.ts. */
  duplicateOf?: { valueStreamId: string; title: string } | null
  duplicateCheckDegraded?: boolean
}

export interface SimvsmImportPreview {
  parserVersion: string
  mappingVersion: string
  families: PreviewFamily[]
  orphanSvgFileNames: string[]
}

export interface PreviewJsonFileInput {
  fileName: string
  text: string
}

export interface BuildPreviewInput {
  jsonFiles: PreviewJsonFileInput[]
  svgFileNames: string[]
}

function computeClassCoverage(file: SimVsmParsedFile): { rows: PreviewClassCoverageRow[]; unknownClasses: string[] } {
  const nodeCounts = new Map<string, number>()
  const linkCounts = new Map<string, number>()
  for (const alt of file.alternatives) {
    for (const n of alt.nodes) nodeCounts.set(n.simvsmClass, (nodeCounts.get(n.simvsmClass) ?? 0) + 1)
    for (const l of alt.links) linkCounts.set(l.simvsmClass, (linkCounts.get(l.simvsmClass) ?? 0) + 1)
  }

  const rows: PreviewClassCoverageRow[] = []
  for (const [simvsmClass, count] of nodeCounts) {
    const entry = classifyNode(simvsmClass)
    rows.push({
      simvsmClass,
      kind: 'node',
      count,
      supported: entry?.supported ?? false,
      targetLabelDe: entry?.labelDe ?? simvsmClass,
      confidence: entry && entry.supported ? entry.confidence : 0,
    })
  }
  for (const [simvsmClass, count] of linkCounts) {
    const entry = classifyLink(simvsmClass)
    rows.push({
      simvsmClass,
      kind: 'link',
      count,
      supported: entry?.supported ?? false,
      targetLabelDe: entry?.labelDe ?? simvsmClass,
      confidence: entry?.supported ? 1 : 0,
    })
  }
  rows.sort((a, b) => b.count - a.count)

  const unknownClasses = [
    ...findUnknownNodeClasses(Object.fromEntries(nodeCounts)),
    ...findUnknownLinkClasses(Object.fromEntries(linkCounts)),
  ]
  return { rows, unknownClasses }
}

function toPreviewStream(s: MappedStream): PreviewStream {
  return {
    index: s.index,
    name: s.name,
    isMainFlag: s.isMainFlag,
    scenarioRole: s.scenarioRole,
    hasResultData: s.hasResultData,
    modificationTime: s.modificationTime,
    nodeCount: s.nodeMappings.length,
    mappedNodeCount: s.nodes.length,
    connectionCount: s.linkMappings.length,
    mappedConnectionCount: s.connections.length,
  }
}

export function buildSimvsmImportPreview(input: BuildPreviewInput): SimvsmImportPreview {
  const parsed = input.jsonFiles.map((f) => ({
    fileName: f.fileName,
    sourceSignature: computeSourceSignature(f.text),
    result: parseSimVsmJsonText(f.text, f.fileName),
  }))

  const okFiles = parsed.filter((p) => p.result.ok) as Array<{ fileName: string; sourceSignature: string; result: SimVsmParsedFile }>
  const pairing = pairSvgFiles(
    okFiles.map((p) => ({ fileName: p.fileName, modelName: p.result.modelName })),
    input.svgFileNames,
  )
  const relatedGroups = groupRelatedFileNames(input.jsonFiles.map((f) => f.fileName))
  const groupKeyByFileName = new Map<string, string[]>()
  for (const group of relatedGroups.values()) {
    for (const fileName of group) groupKeyByFileName.set(fileName, group)
  }

  const families: PreviewFamily[] = parsed.map((p) => {
    if (!p.result.ok) {
      return {
        fileName: p.fileName,
        sourceSignature: p.sourceSignature,
        parseOk: false,
        parseError: p.result.error,
        pairedSvgFileNames: [],
        relatedFileNames: [],
        streams: [],
        classCoverage: [],
        unknownClasses: [],
        report: buildImportReport([]),
      }
    }

    const file = p.result
    const currentIndex = resolveCurrentStreamIndex(file.alternatives)
    // FILE-scoped (not per-alternative) correlation map — see streams.ts
    // module header: the SAME SimVSM node `key` gets the SAME VsmNode id in
    // every alternative of THIS file, fresh per file (this Map is created
    // anew for every family in this .map(), never shared across files).
    const nodeIdByKeyAcrossFile = new Map<string, string>()
    const mappedStreams = file.alternatives.map((alt) => mapStream(alt, currentIndex, undefined, nodeIdByKeyAcrossFile))
    const coverage = computeClassCoverage(file)
    const relatedFileNames = (groupKeyByFileName.get(p.fileName) ?? []).filter((n) => n !== p.fileName)

    return {
      fileName: p.fileName,
      sourceSignature: p.sourceSignature,
      parseOk: true,
      modelName: file.modelName,
      description: file.description,
      createdWithVersion: file.createdWithVersion,
      mainVersion: file.mainVersion,
      pairedSvgFileNames: pairing.pairedSvgFileNamesByJsonFileName[p.fileName] ?? [],
      relatedFileNames,
      streams: mappedStreams.map(toPreviewStream),
      classCoverage: coverage.rows,
      unknownClasses: coverage.unknownClasses,
      report: buildImportReport(mappedStreams),
    }
  })

  return {
    parserVersion: PARSER_VERSION,
    mappingVersion: MAPPING_VERSION,
    families,
    orphanSvgFileNames: pairing.orphanSvgFileNames,
  }
}

/** Re-derives the SAME mapped streams a preview computed for one file — used
 * by the confirm route so it re-parses from re-uploaded bytes rather than
 * ever trusting a client-supplied node/connection array (same "never trust
 * the client wholesale" posture lib/qaf-value-stream/internal/creation.ts
 * documents for its own confirm path). */
export function mapFileToStreams(text: string, fileName: string): { sourceSignature: string; result: ReturnType<typeof parseSimVsmJsonText>; streams: MappedStream[] } {
  const sourceSignature = computeSourceSignature(text)
  const result = parseSimVsmJsonText(text, fileName)
  if (!result.ok) return { sourceSignature, result, streams: [] }
  const currentIndex = resolveCurrentStreamIndex(result.alternatives)
  // Same file-scoped correlation map as buildSimvsmImportPreview above — the
  // confirm route re-parses+re-maps the SAME file, so its node ids must stay
  // correlated across the file's alternatives too (matters when more than
  // one stream of the same file is selected in one confirm call).
  const nodeIdByKeyAcrossFile = new Map<string, string>()
  const streams = result.alternatives.map((alt) => mapStream(alt, currentIndex, undefined, nodeIdByKeyAcrossFile))
  return { sourceSignature, result, streams }
}
