import { escapeHtml } from "./helpers";

/**
 * A word whose value is text with provenance- this literal value appeared in the source code,
 * and was neither quoted nor the result of any backslash, variable, or command substitutions.
 *
 * This provides a level of intentionality that commands can use to distinguish switches:
 *
 * ```tcl
 * puts -stderr text ;# -stderr can be interpreted as a flag and is not part of the message to print
 * puts "-stderr" text ;# -stderr is not a flag, but is part of the message to print
 * puts [return -stderr] text ;# -stderr is not a flag, but is part of the message to print
 * puts $var text ;# The value of $var is part of the message to print, even if the value happens to be "-stderr"
 * ```
 */
export type BareWord = {
  bare: string;
};

/**
 * A word whose value is plain text, with no special provenance.
 */
export type TextWord = {
  text: string;
};

/**
 * A word whose value is "safe" HTML using allowlisted elements/attributes/styles,
 * suitable for inclusion in HTML output with no escaping.
 */
export type HtmlWord = {
  html: string;
};

export type ErrorResult = {
  error: string;
};

export type TextPiece = BareWord | TextWord | HtmlWord;

export type VariablePiece = { variable: string };
export type ScriptPiece = { script: Script };
export type InterpolatedPiece = TextPiece | VariablePiece | ScriptPiece;

export type ProcResult = TextPiece | ErrorResult;

/**
 * A word whose value needs to be determined by evaluating some combination of variable and command
 * substitutions, and concatenating the results with any literal spans.
 */
export type InterpolatedWord = {
  pieces: InterpolatedPiece[];
};

export function AsText(word: TextPiece): string {
  if ("bare" in word) {
    return word.bare;
  } else if ("text" in word) {
    return word.text;
  } else if ("html" in word) {
    return word.html;
  } else {
    return "";
  }
}
export function AsHtml(word: TextPiece | ErrorResult): string {
  if ("bare" in word) {
    return escapeHtml(word.bare);
  } else if ("text" in word) {
    return escapeHtml(word.text);
  } else if ("html" in word) {
    return word.html;
  } else if ("error" in word) {
    return '<span style="color: red;">' + escapeHtml(word.error) + "</span>";
  } else {
    return "";
  }
}

// safely concatenate text pieces, converting as needed
export function Concat(left: TextPiece | null, right: TextPiece) {
  if (left === null) {
    return "bare" in right ? { text: right.bare } : right;
  }
  if ("html" in left || "html" in right) {
    return { html: AsHtml(left) + AsHtml(right) };
  } else {
    return { text: AsText(left) + AsText(right) };
  }
}

function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece {
  return piece ? "text" in piece || "bare" in piece || "html" in piece : false;
}

export function SimplifyWord(
  pieces: InterpolatedPiece[]
): InterpolatedWord | BareWord | TextWord | HtmlWord {
  const consolidated: InterpolatedPiece[] = [];
  for (const piece of pieces) {
    const top = consolidated[consolidated.length - 1];
    if (IsTextPiece(top) && IsTextPiece(piece)) {
      consolidated[consolidated.length - 1] = Concat(top, piece);
    } else {
      consolidated.push(piece);
    }
  }

  if (consolidated.length == 0) {
    return { text: "" };
  } else if (consolidated.length == 1 && IsTextPiece(consolidated[0])) {
    return consolidated[0];
  } else {
    return { pieces: consolidated };
  }
}

export type Word = BareWord | TextWord | HtmlWord | InterpolatedWord;
export type Command = Word[];
export type Script = Command[];