import { escapeRegex } from "./utils.ts"

export function matchLine(pattern: string, actual: string): boolean {
  if (!pattern.includes("...")) return pattern === actual

  // Convert inline ... to regex
  const parts = pattern.split("...")
  const escaped = parts.map(p => escapeRegex(p))
  const regex = new RegExp("^" + escaped.join(".*") + "$")
  return regex.test(actual)
}

export function matchOutput(
  expected: string[],
  actual: string[],
): boolean {
  return doMatch(expected, 0, actual, 0)
}

function doMatch(
  expected: string[],
  ei: number,
  actual: string[],
  ai: number,
): boolean {
  // Both exhausted — match
  if (ei === expected.length && ai === actual.length) return true

  // Expected exhausted but actual remains — no match
  if (ei === expected.length) return false

  const exp = expected[ei]!

  // Multi-line wildcard
  if (exp === "...") {
    // Try matching zero or more actual lines
    for (let skip = ai; skip <= actual.length; skip++) {
      if (doMatch(expected, ei + 1, actual, skip)) return true
    }
    return false
  }

  // Actual exhausted but expected remains — no match
  if (ai === actual.length) return false

  // Line-level match (with possible inline wildcards)
  if (matchLine(exp, actual[ai]!)) {
    return doMatch(expected, ei + 1, actual, ai + 1)
  }

  return false
}

export type DiffLine = {
  kind: "equal" | "expected" | "actual" | "context"
  text: string
}

export function diff(expected: string[], actual: string[]): DiffLine[] {
  const result: DiffLine[] = []
  let ei = 0
  let ai = 0

  while (ei < expected.length || ai < actual.length) {
    if (ei < expected.length && expected[ei] === "...") {
      // Find where the wildcard ends by looking at next expected line
      const nextExp = ei + 1 < expected.length ? expected[ei + 1] : null
      if (nextExp === null) {
        // ... at end matches everything remaining
        result.push({ kind: "context", text: "..." })
        break
      }
      // Skip actual lines until we find the next expected match
      result.push({ kind: "context", text: "..." })
      ei++
      while (ai < actual.length && !matchLine(nextExp!, actual[ai]!)) {
        ai++
      }
      continue
    }

    if (ei < expected.length && ai < actual.length) {
      if (matchLine(expected[ei]!, actual[ai]!)) {
        result.push({ kind: "equal", text: actual[ai]! })
        ei++
        ai++
      } else {
        result.push({ kind: "expected", text: expected[ei]! })
        result.push({ kind: "actual", text: actual[ai]! })
        ei++
        ai++
      }
    } else if (ei < expected.length) {
      result.push({ kind: "expected", text: expected[ei]! })
      ei++
    } else {
      result.push({ kind: "actual", text: actual[ai]! })
      ai++
    }
  }

  return result
}
