/**
 * Transport between the web UI and the Rust Core.
 *
 * Every call goes to one named Tauri command; the Rust Core identifies the calling
 * window and checks its permissions. This wrapper is a convenience, NOT a security
 * boundary.
 *
 * Errors are converted to the message format of the Electron 0.1.0 IPC layer
 * ("Error invoking remote method '<channel>': Error: <message>"), because existing UI
 * code and third-party modules parse error codes out of that text.
 */
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import type { CoreErrorPayload } from "./types";

export type Unsubscribe = () => void;

function errorFrom(channel: string, reason: unknown): Error {
  const payload = (reason && typeof reason === "object" ? reason : { message: String(reason ?? "Unknown error") }) as Partial<CoreErrorPayload>;
  const message = typeof payload.message === "string" && payload.message ? payload.message : String(payload.code || "Operation failed.");
  return new Error(`Error invoking remote method '${channel}': Error: ${message}`);
}

/** Invokes a Core command. `channel` is the stable 0.1.0 channel name used in error texts. */
export async function call<T>(command: string, channel: string, payload?: unknown): Promise<T> {
  try {
    return await invoke<T>(command, payload === undefined ? {} : { payload });
  } catch (reason) {
    throw errorFrom(channel, reason);
  }
}

/** Fire-and-forget command (Electron `ipcRenderer.send`): failures are logged by the Core only. */
export function send(command: string, payload: unknown): void {
  void invoke(command, { payload }).catch(() => {});
}

/** Subscribes to a Core push event; returns a synchronous unsubscribe function like 0.1.0. */
export function subscribe<T>(event: string, callback: (payload: T) => void): Unsubscribe {
  if (typeof callback !== "function") return () => {};
  let active = true;
  let unlisten: UnlistenFn | null = null;
  // Without a target a window hears events addressed to every other window as well.
  void listen<T>(event, (message) => { if (active) callback(message.payload); }, { target: { kind: "AnyLabel", label: getCurrentWebview().label } })
    .then((fn) => { if (active) unlisten = fn; else fn(); })
    .catch(() => {});
  return () => {
    active = false;
    if (unlisten) { unlisten(); unlisten = null; }
  };
}

/** Like `subscribe`, but throws for a non-function callback (module API contract of 0.1.0). */
export function subscribeStrict<T>(event: string, callback: (payload: T) => void, what: string): Unsubscribe {
  if (typeof callback !== "function") throw new TypeError(`${what} callback must be a function.`);
  return subscribe(event, callback);
}
