import chalk from "chalk";
import cliSpinners from "cli-spinners";
import log4js from "log4js";
import { writeSync } from "node:fs";
import { colouredLayout } from "log4js/lib/layouts.js";

let spinner = null;
let consoleLogPatched = false;

function patchConsoleLog() {
  if (consoleLogPatched) {
    return;
  }
  const originalLog = console.log.bind(console);
  console.log = (...args) => {
    spinner?.stopSync();
    originalLog(...args);
  };
  consoleLogPatched = true;
}

function formatLogPrefix(logger) {
  const categoryName = logger?.category ?? "App";
  return colouredLayout({
    startTime: new Date(),
    level: log4js.levels.INFO,
    categoryName,
    data: [],
  });
}

function clearSpinnerLineSync() {
  if (!process.stderr.isTTY) {
    return;
  }

  try {
    writeSync(process.stderr.fd, "\r\x1b[2K");
  } catch {
    process.stderr.write("\r\x1b[2K");
  }
}

export function createSpinner() {
  const { frames, interval } = cliSpinners.dots;
  let timer = null;
  let frameIndex = 0;
  let message = "";
  let active = false;
  let activeLogger = null;

  function render() {
    if (!active) {
      return;
    }
    const frame = frames[frameIndex];
    frameIndex = (frameIndex + 1) % frames.length;
    const prefix = formatLogPrefix(activeLogger);
    const line = `${prefix}${frame} ${chalk.bold.cyan(message)}`;
    process.stderr.write(`\r\x1b[2K${line}`);
  }

  function stopSync() {
    if (timer) {
      clearInterval(timer);
      timer = null;
    }

    const wasActive = active;
    active = false;
    activeLogger = null;

    if (wasActive) {
      clearSpinnerLineSync();
    }
  }

  async function stop() {
    await new Promise((resolve) => setTimeout(resolve, 15));
    stopSync();
  }

  function start(text, logger, timeout = true) {
    if (timeout == true) {
      setTimeout(() => {
        start(text, logger, false);
      }, 10);
      return;
    }
    stopSync();
    message = text;
    frameIndex = 0;
    activeLogger = logger;

    if (!process.stderr.isTTY) {
      const prefix = formatLogPrefix(activeLogger);
      process.stderr.write(
        `${prefix}${frames[0]} ${chalk.bold.cyan(message)}\n`,
      );
      return;
    }

    active = true;
    render();
    timer = setInterval(render, interval);
  }

  return { start, stop, stopSync };
}

export function registerSpinner(instance) {
  spinner = instance;
  patchConsoleLog();
}

export function startWaiting(message, logger) {
  spinner?.start(message, logger);
}

export async function stopWaiting() {
  await spinner?.stop();
}
