import type { RenderResult } from "./types";
import { UpdateScheduler } from "./scheduler";

export interface LifecycleHost {
  dispatch(callback: () => boolean): void;
  mount(child: LifecycleChild): void;
  unmount(): void;
}

export interface LifecycleChild {
  devStrictMode?: boolean;
  setVersion(version: number): void;
  render(): RenderResult;
  commit(render: RenderResult): void;
}

export class SchedulerLifecycleHost implements LifecycleHost {
  private _scheduler: UpdateScheduler;
  private _queue: (() => void)[] = [];
  private _isMounted = false;
  private _child: LifecycleChild | null = null;

  constructor() {
    this._scheduler = new UpdateScheduler(() => this._handleUpdate());
  }

  private _handleUpdate(): void {
    const child = this._child;
    if (!child) return;

    child.setVersion(2);
    child.setVersion(1);

    for (const callback of this._queue) {
      if (child.devStrictMode) {
        callback();
      }

      callback();
    }
    this._queue.length = 0;

    if (child.devStrictMode) {
      void child.render();
    }

    const render = child.render();

    if (this._scheduler.isDirty) {
      throw new Error("Scheduler is dirty after render");
    }

    if (this._isMounted) {
      child.commit(render);
    }
  }

  dispatch(callback: () => boolean): void {
    if (!this._scheduler.isDirty && !callback()) return;
    this._queue.push(callback);
    this._scheduler.markDirty();
  }

  mount(child: LifecycleChild): void {
    this._child = child;
    this._isMounted = true;
  }

  unmount(): void {
    this._isMounted = false;
  }
}

export class CompositeHookLifecycle {
  private _hosts: LifecycleHost[] = [];
  private _child: LifecycleChild | null = null;
  private _scheduler = new SchedulerLifecycleHost();
  private _version = 0;

  get version(): number {
    return this._version;
  }

  mount(rawChild: LifecycleChild): void {
    const child: LifecycleChild = {
      devStrictMode: rawChild.devStrictMode === true,
      setVersion: (v) => {
        this._version = v;
        rawChild.setVersion(v);
      },
      render: () => rawChild.render(),
      commit: (render) => rawChild.commit(render),
    };
    this._child = child;

    const activeHost = this._hosts[0];
    if (activeHost) {
      activeHost.mount(child);
      activeHost.dispatch(() => true);
    } else {
      this._scheduler.mount(child);
    }
  }

  unmount(): void {
    const activeHost = this._hosts[0];
    if (activeHost) {
      activeHost.unmount();
    } else {
      this._scheduler.unmount();
    }
    this._child = null;
  }

  dispatch(callback: () => boolean): void {
    const activeHost = this._hosts[0];
    if (activeHost) {
      activeHost.dispatch(callback);
    } else {
      this._scheduler.dispatch(callback);
    }
  }

  registerHost(host: LifecycleHost): () => void {
    const wasEmpty = this._hosts.length === 0;
    this._hosts.push(host);

    if (wasEmpty && this._child) {
      this._scheduler.unmount();
      host.mount(this._child);
      host.dispatch(() => true);
    }

    return () => {
      const idx = this._hosts.indexOf(host);
      if (idx === -1) return;
      const wasActive = idx === 0;
      this._hosts.splice(idx, 1);

      if (wasActive) {
        host.unmount();
        const next = this._hosts[0];
        if (next && this._child) {
          next.mount(this._child);
          next.dispatch(() => true);
        } else if (this._child) {
          this._scheduler.dispatch(() => true);
          this._scheduler.mount(this._child);
        }
      }
    };
  }
}
