import type {
  NativeAdapter,
  NativeCapability,
  NativePlatform,
  NativeRunOptions,
  NativeTarget,
  NativeBrowserRuntime,
} from "./types.ts";

export class NativeUnavailableError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "NativeUnavailableError";
  }
}

function capacitor():
  | {
      isNativePlatform?: () => boolean;
      getPlatform?: () => string;
    }
  | undefined {
  if (typeof globalThis === "undefined") return undefined;
  return (globalThis as typeof globalThis & { Capacitor?: object }).Capacitor;
}

export function isMobile(): boolean {
  const bridge = capacitor();
  return (
    bridge?.isNativePlatform?.() === true ||
    ["ios", "android"].includes(bridge?.getPlatform?.() ?? "")
  );
}

export function platform(): NativePlatform {
  const bridge = capacitor();
  if (isMobile()) return bridge?.getPlatform?.() ?? "webview";
  return typeof window === "undefined" ? "server" : "browser";
}

const capabilities = new Map<string, NativeCapability>();

function browserRuntime(): NativeBrowserRuntime | undefined {
  if (typeof globalThis === "undefined") return undefined;
  return (globalThis as typeof globalThis & { WrNexusNative?: NativeBrowserRuntime }).WrNexusNative;
}

function pendingRegistrations(): Map<string, NativeCapability> | undefined {
  if (typeof window === "undefined") return undefined;
  const root = globalThis as typeof globalThis & {
    __WrNexusNativePending?: Map<string, NativeCapability>;
  };
  return (root.__WrNexusNativePending ??= new Map());
}

export function register<TOptions = unknown, TResult = unknown>(
  name: string,
  capability: NativeCapability<TOptions, TResult>,
): () => void {
  if (!/^[a-z][a-z0-9.-]*$/i.test(name))
    throw new TypeError(`Invalid native capability name: ${name}`);
  const value = capability as NativeCapability;
  capabilities.set(name, value);
  const runtime = browserRuntime();
  if (runtime) runtime.register(name, value);
  else pendingRegistrations()?.set(name, value);
  return () => {
    capabilities.delete(name);
    const activeRuntime = browserRuntime();
    if (activeRuntime) activeRuntime.unregister(name);
    else pendingRegistrations()?.delete(name);
  };
}

export function registered(): string[] {
  return [...capabilities.keys()].sort();
}

function adapter(name: string, target: NativeTarget): NativeAdapter | undefined {
  return capabilities.get(name)?.[target];
}

export function supports(
  name: string,
  target: NativeTarget = isMobile() ? "mobile" : "browser",
): boolean {
  const value = adapter(name, target);
  return !!value && (value.supported?.() ?? true);
}

export async function run<TResult = unknown>(
  name: string,
  options?: unknown,
  runOptions: NativeRunOptions = {},
): Promise<TResult> {
  const target = runOptions.target ?? (isMobile() ? "mobile" : "browser");
  const value = adapter(name, target);
  if (!value || !(value.supported?.() ?? true)) {
    throw new NativeUnavailableError(`Native capability "${name}" is unavailable on ${target}`);
  }
  return (await value.run(options)) as TResult;
}

export function clearRegistry(): void {
  capabilities.clear();
}
