/* eslint-disable @typescript-eslint/no-explicit-any */

import React from "react";

/**
 * All log messages will be kept in an ordered stream, but the actual
 * console has different methods.
 *
 * These enumerates keep the mark.
 */
export enum ConsoleLevel {
  Log = "log",
  Debug = "debug",
  Info = "info",
  Warn = "warn",
  Error = "error",
}

export type ConsoleMessage = {
  when: Date;
  level: ConsoleLevel;
  data: any[];
};

/**
 * Flinger flings logs from the browser to buffers, as well as letting
 * the console operate as normal.
 *
 * This patches `log`, `debug`, `info`, `warn`, and `error.
 */
export type Flinger = {
  uninstall: () => void;
  buffer: ConsoleMessage[];
};

/**
 * A buffer of console messages.
 */
export type ConsoleMessages = ConsoleMessage[];

/**
 * Install flinger into your page, intecepting console.log.
 *
 * This returns a function to un-install.
 */
export const install = (buffer: ConsoleMessages): Flinger => {
  // capture initial functions for later restoration
  const originals = Object.values(ConsoleLevel).map((level) => ({
    level,
    fun: console[level],
  }));

  // grab methods and replace them with interceptors
  Object.values(ConsoleLevel).forEach((level) => {
    const forwardTo = console[level];
    // this is it -- not a lot too it
    console[level] = (...data: any[]): void => {
      const message = {
        when: new Date(),
        level,
        /**
         * Needed cause folks can -- render things that will not make it
         * out to JSON
         */
        data: data?.map((d) => `${d}`),
      };
      buffer.push(message);
      // remember to splat out that array otherwise you log arrays...
      forwardTo(...data);
    };
  });

  return {
    uninstall: () => {
      originals.forEach((original) => (console[original.level] = original.fun));
    },
    buffer,
  };
};

/**
 * Our course we need a custom hook.
 */
export const useFlinger = (
  initialBuffer: ConsoleMessage[]
): ConsoleMessage[] => {
  const buffer = React.useRef<ConsoleMessage[]>(initialBuffer);

  // install / uninstall startup effect
  React.useEffect(() => {
    const flinger = install(buffer.current);
    return flinger.uninstall;
  }, []);

  return buffer.current;
};
