import { createPatch } from "diff"
import { failures, SassResult, TestResult } from "./util"

/**
 * Normalize the output of the Sass compiler.
 * Standardizes the number and type of newlines and the paths of input files.
 */
export function normalizeOutput(output: string): string {
  return (
    output
      .replace(/(\r?\n)+/g, "\n")
      // Normalize paths
      .replace(/[-_/a-zA-Z0-9]+(input\.s[ca]ss)/g, "$1")
      .trim()
  )
}

/**
 * Extract the error message of a Sass compiler.
 */
export function extractErrorMessage(msg: string): string {
  return (
    normalizeOutput(msg)
      .split("\n")
      .find((line) => line.startsWith("Error:")) ?? ""
  )
}

/**
 * Extract the warning message(s) of a Sass compiler.
 */
export function extractWarningMessages(msg: string): string {
  // TODO fix warning extraction
  // This implementation replicates behavior in the ruby runner, which is broken right now
  // and compares only the first line of the first warning.
  // return (
  //   normalizeOutput(msg)
  //     .split("\n\n")
  //     .filter((line) => /^\s*(DEPRECATION )?WARNING/.test(line))
  //     .join("\n\n")
  return (
    normalizeOutput(msg)
      .split("\n")
      .find((line) => /^\s*(DEPRECATION )?WARNING/.test(line)) ?? ""
  )
}

function getDiff(
  filename: string,
  expected: string = "",
  actual: string = "",
  normalizer: (text: string) => string = normalizeOutput
): string | undefined {
  expected = normalizer(expected)
  actual = normalizer(actual)
  if (expected === actual) {
    return
  }
  return createPatch(filename, expected, actual, "expected", "actual")
}

interface CompareOptions {
  /**
   * if true, errors and warnings will be trimmed
   * so only the messages are compared and not line information
   */
  trimErrors?: boolean
  /** If true, skip warning checks */
  skipWarning?: boolean
}

/**
 * Compare the provided expected and actual results.
 * @param expected the expected results to check
 * @param actual the actual results to check
 * @param options options for comparison
 */
export function compareResults(
  expected: SassResult,
  actual: SassResult,
  { skipWarning, trimErrors }: CompareOptions
): TestResult {
  if (expected.isSuccess) {
    if (!actual.isSuccess) {
      return failures.UnexpectedError()
    }

    const diff = getDiff("output.css", expected.output, actual.output)
    if (diff) {
      return failures.OutputDifference(diff)
    }

    if ((expected.warning || actual.warning) && !skipWarning) {
      const normalizer = trimErrors ? extractWarningMessages : normalizeOutput
      const diff = getDiff(
        "warning",
        expected.warning,
        actual.warning,
        normalizer
      )
      if (diff) {
        return failures.WarningDifference(diff)
      }
    }
  } else {
    if (actual.isSuccess) {
      return failures.UnexpectedSuccess()
    }
    const normalizer = trimErrors ? extractErrorMessage : normalizeOutput
    const diff = getDiff("error", expected.error, actual.error, normalizer)
    if (diff) {
      return failures.ErrorDifference(diff)
    }
  }

  return { type: "pass" }
}