// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
/* eslint-disable @typescript-eslint/no-explicit-any */

/* eslint-disable no-await-in-loop */
import Debug from "debug";
import { mutableProxyFactory } from "./proxy.js";

const debug = Debug("montar");

export type Start<T> = () => Promise<T>;

interface StateMeta<T> {
  name: string;
  start: Start<T>;
  stop(): Promise<void>;
  isFunction: boolean;
  setHandler<T extends object>(handler: ProxyHandler<T>): void;
  setTarget<T>(target: T): void;
}

interface DefState<T> {
  start: Start<T>;
  stop?(): Promise<void>;
  isFunction?: boolean;
}
interface StateMetaMap<T> {
  [name: string]: StateMeta<T>;
}

interface StateMap {
  [name: string]: any;
}

const defaultStop = async () => {};
const states: StateMap = {};
const statesMeta: StateMetaMap<any> = {};
const statesOrder: string[] = [];

function getState(name: string) {
  if (!statesMeta.hasOwnProperty(name)) {
    throw new Error(`State ${name} not started.`);
  }

  return states[name];
}

function setState(name: string, state: any) {
  states[name] = state;
}

function getStateMeta(name: string) {
  return statesMeta[name];
}

function setStateMeta(name: string, meta: StateMeta<any>) {
  statesMeta[name] = meta;
}

export function isStarted(name: string): boolean {
  return getState(name) !== undefined;
}

const canary = {
  error:
    "I am the bare proxy. You shouldn't see me. If you see me, then a montar state was not started.",
};
export function defState<T>(name: string, meta: DefState<T>): any {
  if (statesMeta.hasOwnProperty(name)) {
    throw new Error(`Already registered ${name}`);
  }

  const { start, stop = defaultStop, isFunction = false } = meta;

  let initialTarget: any = canary;
  if (isFunction) initialTarget = () => canary.error;
  const { proxy, setTarget, setHandler } = mutableProxyFactory(initialTarget);

  setStateMeta(name, {
    name,
    start,
    stop,
    setTarget,
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    setHandler,
    isFunction,
  });
  statesOrder.push(name);

  debug(`defined: ${name}`);
  return proxy;
}

async function _start(toStart?: string[]): Promise<void> {
  for (const name of toStart) {
    debug(` >> starting..  ${name}`);
    const meta = getStateMeta(name);
    const state = await meta.start();
    const stateType = typeof state;
    if (!["object", "function"].includes(stateType)) {
      throw new Error(
        `error ${name}'s start() returned a non-object, non-function type`
      );
    }

    meta.setTarget(state);
    setState(name, state);
  }
}

export async function start(): Promise<void> {
  return _start(statesOrder);
}

export async function startWithout(excluded: string[]): Promise<void> {
  const toStart = statesOrder.filter((name) => !excluded.includes(name));
  return _start(toStart);
}

export async function startOnly(included: string[]): Promise<void> {
  const toStart = statesOrder.filter((name) => included.includes(name));
  debug(" startOnly ", included);
  return _start(toStart);
}

export async function stop(): Promise<void> {
  for (let i = statesOrder.length - 1; i >= 0; i--) {
    const name = statesOrder[i];
    if (states[name] === undefined) continue;
    const meta = statesMeta[name];
    await meta.stop();
    delete states[name];
    debug(`<< stopping..  ${name}`);
  }
}
