export function splitLines(input: string) {
  const lines = input.match(/.*(?:$|\r?\n)/g).filter(Boolean); // Split lines and keep newline character
  if (lines.length > 0 && lines[lines.length - 1].endsWith("\n")) {
    // Keep last empty line
    lines.push("");
  }
  return lines;
}

export function splitWords(input: string) {
  return input.match(/\w+|\W+/g).filter(Boolean); // Split consecutive words and non-words
}

export function isBlank(input: string) {
  return input.trim().length === 0;
}

export const autoClosingPairs = [
  ["(", ")"],
  ["[", "]"],
  ["{", "}"],
  ["'", "'"],
  ['"', '"'],
  ["`", "`"],
];

export const autoClosingPairOpenings = autoClosingPairs.map((pair) => pair[0]);
export const autoClosingPairClosings = autoClosingPairs.map((pair) => pair[1]);

// FIXME: This function is not good enough, it can not handle escaped characters.
export function findUnpairedAutoClosingChars(input: string): string {
  const stack: string[] = [];

  for (const char of input) {
    [
      ["(", ")"],
      ["[", "]"],
      ["{", "}"],
    ].forEach((pair) => {
      if (char === pair[1]) {
        if (stack.length > 0 && stack[stack.length - 1] === pair[0]) {
          stack.pop();
        } else {
          stack.push(char);
        }
      }
    });
    if ("([{".includes(char)) {
      stack.push(char);
    }
    ["'", '"', "`"].forEach((quote) => {
      if (char === quote) {
        if (stack.length > 0 && stack.includes(quote)) {
          stack.splice(stack.lastIndexOf(quote), stack.length - stack.lastIndexOf(quote));
        } else {
          stack.push(char);
        }
      }
    });
  }
  return stack.join("");
}

// Using string levenshtein distance is not good, because variable name may create a large distance.
// Such as distance is 9 between `const fooFooFoo = 1;` and `const barBarBar = 1;`, but maybe 1 is enough.
// May be better to count distance based on words instead of characters.
import * as levenshtein from "fast-levenshtein";
export function calcDistance(a: string, b: string) {
  return levenshtein.get(a, b);
}

// Polyfill for AbortSignal.any(signals) which added in Node.js v20.
export function abortSignalFromAnyOf(signals: AbortSignal[]) {
  const controller = new AbortController();
  for (const signal of signals) {
    if (signal?.aborted) {
      controller.abort(signal.reason);
      return signal;
    }
    signal?.addEventListener("abort", () => controller.abort(signal.reason), {
      signal: controller.signal,
    });
  }
  return controller.signal;
}

// Http Error
export class HttpError extends Error {
  status: number;
  statusText: string;
  response: Response;

  constructor(response: Response) {
    super(`${response.status} ${response.statusText}`);
    this.name = "HttpError";
    this.status = response.status;
    this.statusText = response.statusText;
    this.response = response;
  }
}

export function isTimeoutError(error: any) {
  return (
    (error instanceof Error && error.name === "TimeoutError") ||
    (error instanceof HttpError && [408, 499].indexOf(error.status) !== -1)
  );
}

export function isCanceledError(error: any) {
  return error instanceof Error && error.name === "AbortError";
}
