export interface QafRowForMapping {
  key: string
  prozessbezeichnung: string
  file_name?: string | null
  uploaded_at?: string | null
  qaf_upload_id?: string | null
}

export interface QafFileForMapping {
  id: string
  label: string
  file_name?: string | null
  uploaded_at?: string | null
  rows: QafRowForMapping[]
}

export interface MultiFileMatchResult {
  matched: Map<string, { step_id: string; confidence: number }>
  unmatched: Array<QafRowForMapping & { qaf_upload_id: string }>
}

export function compositeKey(qaf_upload_id: string | null | undefined, row_key: string): string {
  return `${qaf_upload_id ?? '__nofile__'}::${row_key}`
}

export interface ProcessStepForMapping {
  id: string
  step_number: number
  station_name: string
  area_name: string | null
}

export interface AutoMatchResult {
  matched: Map<string, { step_id: string; confidence: number }>
  unmatched: QafRowForMapping[]
}

function normalize(s: string): string {
  return s.trim().toLowerCase()
}

function tokenize(s: string): Set<string> {
  return new Set(
    normalize(s)
      .split(/[\s\-_/,;]+/)
      .filter((t) => t.length > 0),
  )
}

export function scoreCandidate(a: string, b: string): number {
  const A = tokenize(a)
  const B = tokenize(b)
  if (A.size === 0 || B.size === 0) return 0
  if (normalize(a) === normalize(b)) return 1

  let shared = 0
  for (const t of A) {
    if (B.has(t)) shared += 1
  }
  const union = new Set([...A, ...B]).size
  return Number((shared / union).toFixed(3))
}

export function autoMatchQafRows(
  rows: QafRowForMapping[],
  steps: ProcessStepForMapping[],
): AutoMatchResult {
  const stationIndex = new Map<string, string>()
  for (const s of steps) {
    stationIndex.set(normalize(s.station_name), s.id)
  }

  const matched = new Map<string, { step_id: string; confidence: number }>()
  const unmatched: QafRowForMapping[] = []

  for (const row of rows) {
    const key = normalize(row.prozessbezeichnung ?? '')
    if (key.length === 0) {
      unmatched.push(row)
      continue
    }
    const step_id = stationIndex.get(key)
    if (step_id) {
      matched.set(row.key, { step_id, confidence: 1.0 })
    } else {
      unmatched.push(row)
    }
  }

  return { matched, unmatched }
}

export interface FuzzySuggestOptions {
  topN: number
  minScore: number
}

export interface FuzzyCandidate {
  step: ProcessStepForMapping
  score: number
}

export function fuzzySuggest(
  query: string,
  steps: ProcessStepForMapping[],
  options: FuzzySuggestOptions,
): FuzzyCandidate[] {
  const scored: FuzzyCandidate[] = steps
    .map((step) => ({ step, score: scoreCandidate(query, step.station_name) }))
    .filter((c) => c.score >= options.minScore)
    .sort((a, b) => b.score - a.score)
  return scored.slice(0, options.topN)
}

/**
 * Multi-file variant: each row keeps its qaf_upload_id, the returned map
 * is keyed by `compositeKey(qaf_upload_id, row.key)` so the same row_key
 * across different files stays distinct.
 */
export function autoMatchQafFiles(
  files: QafFileForMapping[],
  steps: ProcessStepForMapping[],
): MultiFileMatchResult {
  const stationIndex = new Map<string, string>()
  for (const s of steps) {
    stationIndex.set(normalize(s.station_name), s.id)
  }

  const matched = new Map<string, { step_id: string; confidence: number }>()
  const unmatched: Array<QafRowForMapping & { qaf_upload_id: string }> = []

  for (const file of files) {
    for (const row of file.rows) {
      const enriched = { ...row, qaf_upload_id: file.id }
      const key = normalize(row.prozessbezeichnung ?? '')
      const ck = compositeKey(file.id, row.key)
      if (key.length === 0) {
        unmatched.push(enriched)
        continue
      }
      const step_id = stationIndex.get(key)
      if (step_id) {
        matched.set(ck, { step_id, confidence: 1.0 })
      } else {
        unmatched.push(enriched)
      }
    }
  }

  return { matched, unmatched }
}
