// Alternative → Szenario assembly (Wertstrom P6, Baustein 3). Maps every
// SimVSM `alternatives[]` entry ("stream") to a full node/connection set and
// decides its P2 scenario role — reusing the EXISTING P2 data model
// (`ScenarioKind`, `parent_value_stream_id`) verbatim, no schema change.
//
// current-Wahl signal cascade (Review-Fix, adversarial review PR #360):
// §5.2/Capability-Matrix A9 treats SimVSM's `isMain` flag as the natural
// "this is the Ist-Zustand" signal, but real-corpus measurement found
// `isMain: true` on 0 of 73 real alternatives across all 25 files — every
// single one is `false`, including files with only ONE alternative. A pure
// "first alternative in array order" fallback was measurably wrong for at
// least one real corpus file (the array-order-swapped SimVSM auto-dedup
// case below). `resolveCurrentStreamIndex` therefore tries FOUR signals in
// order, each only a tiebreak for when the previous one is silent:
//   1. `isMain: true` (forward-compatible with a future export that sets it).
//   2. An UNSUFFIXED name that is the literal prefix of a sibling's
//      "<name>_N" name wins against that suffixed sibling — SimVSM only
//      appends "_N" when it auto-deduplicated an already-taken name at
//      creation time, so the unsuffixed name is the original/Ist-Zustand.
//   3. Earliest `modificationTime` (parseable ISO timestamp) — the
//      least-recently-edited alternative is the more likely Ist-Zustand
//      baseline, an Alternative being actively worked on tends to be
//      touched more recently.
//   4. First alternative in array order (the original, still-plausible
//      fallback when none of the above apply).
// Surfaced in the preview (never silently assumed) — every PreviewStream
// carries `hasResultData`/`modificationTime` as context so the user can spot
// and correct a bad guess by simply not selecting that stream. P6 still does
// not offer a manual "make this the current one" override control (kept out
// of §14.1's deliberately simple flow, unchanged limitation — see TODO.md).
//
// Node-ID correlation across scenarios (Review-Fix, adversarial review PR
// #360): P2's own precedent (app/api/wertstrom/[id]/duplicate/route.ts)
// deliberately keeps a node's id UNCHANGED when a scenario is derived, so
// P5's comparison (`computeNodeParameterDeviations`, execution-prompt §9.4)
// can match "this node in the Ist" against "the corresponding node in the
// Alternative" by id. SimVSM's own `key` is the same kind of stable
// cross-alternative identity (the SAME real-world node keeps the SAME `key`
// across an Ist-Zustand and its Alternativen). `mapStream` therefore accepts
// an optional FILE-scoped `nodeIdByKeyAcrossFile` map (built once per file
// by the caller, see preview.ts) — the first time a `key` is seen anywhere
// in the file it gets a fresh id, every later occurrence of the SAME key (in
// any other alternative of the SAME file) reuses that SAME id. A genuinely
// NEW node (a key that never appeared in an earlier-mapped alternative)
// still gets its own fresh id. IDs stay fresh per file/family — a fresh Map
// is created per file (see preview.ts), so there is never a collision
// between two different uploaded files.
//
// Positions: SimVSM's own `loc` is intentionally NEVER used to place nodes
// (see types.ts SimVsmParsedNode.loc doc) — every mapped stream is
// positioned via the EXISTING `computeAutoLayout` (components/wertstrom/
// vsm-auto-layout.ts, P3), the same function the Quick-Start-Wizard and the
// editor's "Automatisch anordnen" button already use. Reuse, not
// reinvention — and it sidesteps SimVSM's canvas being able to use negative/
// unbounded coordinates while the editor's world is not (a "visual layout
// difference", §14.4, honestly reported rather than papered over with an ad
// hoc coordinate transform).

import type { VsmConnection, VsmNode } from '@/lib/vsm-types'
import { computeAutoLayout } from '@/components/wertstrom/vsm-auto-layout'
import type { SimVsmParsedAlternative } from './types'
import { mapSimVsmNode, type NodeMapOutcome } from './node-mapper'
import { mapSimVsmLink, type LinkMapOutcome } from './link-mapper'

export type ScenarioRole = 'current' | 'alternative'

export interface MappedStream {
  index: number
  name: string
  isMainFlag: boolean
  scenarioRole: ScenarioRole
  hasResultData: boolean
  /** Raw SimVsmParsedAlternative.modificationTime passthrough — `undefined`
   * when absent. See module header, current-Wahl signal 3 + PreviewStream. */
  modificationTime?: string
  nodes: VsmNode[]
  connections: VsmConnection[]
  nodeMappings: NodeMapOutcome[]
  linkMappings: LinkMapOutcome[]
}

/** True when `maybeSuffixed` is exactly `${base}_<digits>` — SimVSM's own
 * auto-dedup suffix shape (e.g. "Standard" / "Standard_1"). Never matches a
 * base against itself (requires a non-empty digit suffix). Exported only for
 * the real-corpus test's own pair-detection scan (internal/__tests__ — not
 * part of the public lib/simvsm-import barrel). */
export function isSuffixedVariantOf(base: string, maybeSuffixed: string): boolean {
  // Plain string ops, deliberately NOT a dynamically-constructed RegExp
  // (avoids a non-literal-regexp lint warning for no benefit — the match is
  // a simple fixed-prefix + all-digits check, nothing regex expressiveness
  // buys here).
  if (!base || !maybeSuffixed.startsWith(`${base}_`)) return false
  const suffix = maybeSuffixed.slice(base.length + 1)
  return suffix.length > 0 && /^\d+$/.test(suffix)
}

/** Parses `modificationTime` into a comparable epoch — `null` (never NaN)
 * for anything absent/unparseable, so callers can filter rather than risk a
 * NaN silently winning/losing a sort. */
function parseModificationTimeEpoch(modificationTime: string | undefined): number | null {
  if (!modificationTime) return null
  const t = Date.parse(modificationTime)
  return Number.isFinite(t) ? t : null
}

/** Which alternative index plays the "current" (Ist-Zustand) role for a
 * whole file. Four-tier signal cascade — see module header for the full
 * reasoning and the real-corpus evidence behind each tier. */
export function resolveCurrentStreamIndex(alternatives: readonly SimVsmParsedAlternative[]): number {
  // 1) isMain wins outright when present.
  const mainIndex = alternatives.findIndex((a) => a.isMainFlag)
  if (mainIndex >= 0) return mainIndex

  // 2) An unsuffixed name that is the literal prefix of a sibling's
  //    "<name>_N" name wins against that suffixed sibling.
  for (let i = 0; i < alternatives.length; i++) {
    const candidateName = alternatives[i].name
    const hasSuffixedSibling = alternatives.some((other, j) => j !== i && isSuffixedVariantOf(candidateName, other.name))
    if (hasSuffixedSibling) return i
  }

  // 3) Earliest parseable modificationTime wins.
  let earliestIndex = -1
  let earliestEpoch = Infinity
  for (let i = 0; i < alternatives.length; i++) {
    const epoch = parseModificationTimeEpoch(alternatives[i].modificationTime)
    if (epoch !== null && epoch < earliestEpoch) {
      earliestEpoch = epoch
      earliestIndex = i
    }
  }
  if (earliestIndex >= 0) return earliestIndex

  // 4) First alternative in array order.
  return 0
}

let keyCounter = 0
/** Deterministic-enough default id factory (tests inject their own for
 * reproducibility, same convention as lib/qaf-value-stream's IdFactory).
 * Keyed per the mapSimVsmNode contract, but the default factory itself
 * doesn't need the key — correlation is layered on top by mapStream. */
function defaultIdFactory(): string {
  keyCounter += 1
  return `simvsm-${Date.now().toString(36)}-${keyCounter.toString(36)}`
}

/**
 * @param nodeIdByKeyAcrossFile FILE-scoped key→id correlation map (see
 * module header) — omit for a one-off/isolated mapStream call (e.g. a
 * single-alternative test); pass the SAME Map instance across every
 * mapStream() call for one file's alternatives (see preview.ts) so the same
 * SimVSM node keeps the same VsmNode id in every scenario it appears in.
 */
export function mapStream(
  alt: SimVsmParsedAlternative,
  currentIndex: number,
  idFactory: () => string = defaultIdFactory,
  nodeIdByKeyAcrossFile?: Map<string, string>,
): MappedStream {
  const correlationMap = nodeIdByKeyAcrossFile ?? new Map<string, string>()
  const keyedIdFactory = (key: string): string => {
    const existing = correlationMap.get(key)
    if (existing) return existing
    const fresh = idFactory()
    correlationMap.set(key, fresh)
    return fresh
  }

  const nodeMappings = alt.nodes.map((n) => mapSimVsmNode(n, keyedIdFactory))

  const nodeIdByKey = new Map<string, string>()
  for (const m of nodeMappings) {
    if (m.supported && m.vsmNode) nodeIdByKey.set(m.simvsmKey, m.vsmNode.id)
  }

  const linkMappings = alt.links.map((l) => mapSimVsmLink(l, nodeIdByKey, idFactory))

  const nodes = nodeMappings.map((m) => m.vsmNode).filter((n): n is VsmNode => n !== null)
  const connections = linkMappings.map((m) => m.connection).filter((c): c is VsmConnection => c !== null)

  const positions = computeAutoLayout(nodes, connections)
  for (const node of nodes) {
    const pos = positions[node.id]
    if (pos) {
      node.x = pos.x
      node.y = pos.y
    }
  }

  return {
    index: alt.index,
    name: alt.name,
    isMainFlag: alt.isMainFlag,
    scenarioRole: alt.index === currentIndex ? 'current' : 'alternative',
    hasResultData: alt.hasResultData,
    modificationTime: alt.modificationTime,
    nodes,
    connections,
    nodeMappings,
    linkMappings,
  }
}
