import { SpecDirectory } from "../spec-directory"

interface SuccessResult {
  isSuccess: true
  output: string
  warning?: string
}

interface ErrorResult {
  isSuccess: false
  error: string
}

/** A result of executing a sass compiler */
export type SassResult = SuccessResult | ErrorResult

type FailureType =
  // | "todo_warning_nonexistent"
  // | "conflicting_files"
  // | "missing_output"
  | "unexpected_error"
  | "unexpected_success"
  | "output_difference"
  | "error_difference"
  | "warning_difference"
  | "unnecessary_todo"

// TODO split these up into separate types (e.g. `FailureResult | ErrorResult`).
// Spliting up the properties into a union type will be cumbersome when
// results are used as part of a test case across multiple methods (e.g. in `Interactor`).
// When calling `TestResult.result()` across multiple methods with `TestResult` as a signature,
// we need to check each time that we the right type of result before using any sub-properties.
/**
 * The result of a test execution, along with metadata for failures and errors
 */
export interface TestResult {
  type: "pass" | "fail" | "todo" | "skip" | "error"
  // If `fail`, the type of failure, message, and possible diff
  failureType?: FailureType
  message?: string
  diff?: string
  // If `error`, the thrown error
  error?: Error
}

function makeFailureFactory(failureType: FailureType, message: string) {
  return function (diff?: string): TestResult {
    return {
      type: "fail",
      failureType,
      message,
      diff,
    }
  }
}

export const failures = {
  UnexpectedError: makeFailureFactory(
    "unexpected_error",
    "Test case should succeed but it did not"
  ),
  UnexpectedSuccess: makeFailureFactory(
    "unexpected_success",
    "Expected test to fail but it did not"
  ),
  OutputDifference: makeFailureFactory(
    "output_difference",
    "Expected did not match output"
  ),
  WarningDifference: makeFailureFactory(
    "warning_difference",
    "Expected did not match warning"
  ),
  ErrorDifference: makeFailureFactory(
    "error_difference",
    "Expected did not match error"
  ),
  UnnecessaryTodo: makeFailureFactory(
    "unnecessary_todo",
    "Expected test marked TODO to fail but it passed"
  ),
}

export function getExpectedFiles(impl?: string): string[] {
  return impl
    ? [`output-${impl}.css`, `warning-${impl}`, `error-${impl}`]
    : ["output.css", "warning", "error"]
}

// Overwrite the set of results to be equal to the provided result
export async function overwriteResults(
  dir: SpecDirectory,
  actual: SassResult,
  impl?: string
): Promise<void> {
  const [outputFile, warningFile, errorFile] = getExpectedFiles(impl)
  if (actual.isSuccess) {
    await Promise.all([
      dir.writeFile(outputFile, actual.output),
      actual.warning
        ? dir.writeFile(warningFile, actual.warning)
        : dir.removeFile(warningFile),
      dir.removeFile(errorFile),
    ])
  } else {
    await Promise.all([
      dir.writeFile(errorFile, actual.error),
      dir.removeFile(outputFile),
      dir.removeFile(warningFile),
    ])
  }
}
