import { Duration, asyncMap, asyncZip, enforce_nonnull, pause } from "vscript";
import * as VAPI from "../artefacts/vapi/index.js";
import { get_peaks } from "./audio.js";

function add_db(dbs: number[]) {
  return (
    10 *
    Math.log10(
      dbs.reduce((accu, currval) => {
        return accu + Math.pow(10, currval / 10);
      }, 0),
    )
  );
}
export type AnyMixer =
  | VAPI.AT1130.AudioEngine.MonoMixAsNamedTableRow
  | VAPI.AT1130.AudioEngine.StereoMixAsNamedTableRow;

type ArrayElement<ArrayType extends readonly unknown[]> =
  ArrayType extends readonly (infer ElementType)[] ? ElementType : never;

export class AutoMixer {
  promise: null | Promise<void>;
  private _mixer: AnyMixer;
  updateInterval: Duration = new Duration(20, "ms");

  public get mixer(): AnyMixer {
    return this._mixer;
  }
  private enable: boolean = true;
  constructor(mixer: AnyMixer) {
    this._mixer = mixer;
    this.promise = this.mix();
  }

  async start() {
    if (this.enable && this.promise) return;
    this.enable = true;
    this.promise = this.mix();
  }

  async stop() {
    this.enable = false;
    await this.promise;
    this.promise = null;
  }
  private async mix() {
    const get_single_ppm = async (
      channel: ArrayElement<Awaited<ReturnType<typeof this.mixer.inputs.read>>>,
    ) => {
      if (!(!!channel && !!(channel as any).enclosing_subtree)) return -1024;
      const audio_essence = (channel as any).enclosing_subtree as VAPI.AT1130.Audio.Essence;
      const peaks = await get_peaks(audio_essence);
      return (peaks[(channel as any).index] ?? -1024 * 10) / 10;
    };

    const caps = enforce_nonnull(await this.mixer.capabilities.status.read());
    while (this.enable) {
      const inputs = (await this.mixer.inputs.read()).slice(0, caps.channels);
      const levels = await asyncMap(inputs, async (inp) => {
        return await get_single_ppm(inp);
      });
      const sum_levels = add_db(levels);
      await asyncZip(levels, await this.mixer.faders.rows(), async (in_level, fader, idx) => {
        if (idx >= caps?.channels) return;
        const target_gain = Math.min(in_level - sum_levels, 12.0); // division of logs => subtraction
        await fader.gain.write(target_gain);
      });
      await pause(this.updateInterval);
    }
  }
}
