///|
const STATUS_NOT_AVAILABLE : Int = 1

///|
const STATUS_PERMISSION_DENIED : Int = 2

///|
const STATUS_BACKEND_FAILURE : Int = 3

///|
async fn[T, E : Error] suspend(
  f : ((T) -> Unit, (E) -> Unit) -> Unit,
) -> T raise E = "%async.suspend"

///|
extern "js" fn js_read_text(
  on_ok : (String) -> Unit,
  on_err : (Int, String) -> Unit,
) -> Unit =
  #|(onOk, onErr) => {
  #|  const g = globalThis;
  #|  const api = g?.navigator?.clipboard;
  #|  const isBrowser = !!api && typeof api.readText === "function";
  #|  if (isBrowser) {
  #|    api.readText().then(
  #|      (text) => onOk(String(text)),
  #|      (err) => {
  #|        const name = err && err.name ? String(err.name) : "";
  #|        const message = err && err.message ? String(err.message) : String(err);
  #|        if (name === "NotAllowedError" || name === "SecurityError") {
  #|          onErr(2, message);
  #|        } else {
  #|          onErr(3, message);
  #|        }
  #|      }
  #|    );
  #|    return;
  #|  }
  #|  const isNode = !!g?.process?.versions?.node;
  #|  if (!isNode) {
  #|    onErr(1, "Clipboard API is unavailable");
  #|    return;
  #|  }
  #|  const loadClipboardy =
  #|    typeof require === "function"
  #|      ? Promise.resolve().then(() => require("clipboardy"))
  #|      : import("clipboardy");
  #|  loadClipboardy
  #|    .then((mod) => {
  #|      const clipboardy = mod && mod.default ? mod.default : mod;
  #|      if (!clipboardy || typeof clipboardy.read !== "function") {
  #|        throw { name: "ClipboardUnavailable", message: "clipboardy.read is unavailable" };
  #|      }
  #|      return clipboardy.read();
  #|    })
  #|    .then(
  #|      (text) => onOk(String(text)),
  #|      (err) => {
  #|        const code = err && err.code ? String(err.code) : "";
  #|        const name = err && err.name ? String(err.name) : "";
  #|        const message = err && err.message ? String(err.message) : String(err);
  #|        if (
  #|          code === "MODULE_NOT_FOUND" ||
  #|          code === "ERR_MODULE_NOT_FOUND" ||
  #|          name === "ClipboardUnavailable"
  #|        ) {
  #|          onErr(1, message);
  #|        } else if (name === "NotAllowedError" || name === "SecurityError") {
  #|          onErr(2, message);
  #|        } else {
  #|          onErr(3, message);
  #|        }
  #|      }
  #|    );
  #|}

///|
extern "js" fn js_write_text(
  text : String,
  on_ok : () -> Unit,
  on_err : (Int, String) -> Unit,
) -> Unit =
  #|(text, onOk, onErr) => {
  #|  const g = globalThis;
  #|  const api = g?.navigator?.clipboard;
  #|  const isBrowser = !!api && typeof api.writeText === "function";
  #|  if (isBrowser) {
  #|    api.writeText(text).then(
  #|      () => onOk(),
  #|      (err) => {
  #|        const name = err && err.name ? String(err.name) : "";
  #|        const message = err && err.message ? String(err.message) : String(err);
  #|        if (name === "NotAllowedError" || name === "SecurityError") {
  #|          onErr(2, message);
  #|        } else {
  #|          onErr(3, message);
  #|        }
  #|      }
  #|    );
  #|    return;
  #|  }
  #|  const isNode = !!g?.process?.versions?.node;
  #|  if (!isNode) {
  #|    onErr(1, "Clipboard API is unavailable");
  #|    return;
  #|  }
  #|  const loadClipboardy =
  #|    typeof require === "function"
  #|      ? Promise.resolve().then(() => require("clipboardy"))
  #|      : import("clipboardy");
  #|  loadClipboardy
  #|    .then((mod) => {
  #|      const clipboardy = mod && mod.default ? mod.default : mod;
  #|      if (!clipboardy || typeof clipboardy.write !== "function") {
  #|        throw { name: "ClipboardUnavailable", message: "clipboardy.write is unavailable" };
  #|      }
  #|      return clipboardy.write(text);
  #|    })
  #|    .then(
  #|      () => onOk(),
  #|      (err) => {
  #|        const code = err && err.code ? String(err.code) : "";
  #|        const name = err && err.name ? String(err.name) : "";
  #|        const message = err && err.message ? String(err.message) : String(err);
  #|        if (
  #|          code === "MODULE_NOT_FOUND" ||
  #|          code === "ERR_MODULE_NOT_FOUND" ||
  #|          name === "ClipboardUnavailable"
  #|        ) {
  #|          onErr(1, message);
  #|        } else if (name === "NotAllowedError" || name === "SecurityError") {
  #|          onErr(2, message);
  #|        } else {
  #|          onErr(3, message);
  #|        }
  #|      }
  #|    );
  #|}

///|
fn raise_for_status(
  status : Int,
  message : String,
) -> Unit raise ClipboardError {
  match status {
    0 => ()
    STATUS_NOT_AVAILABLE => raise NotAvailable
    STATUS_PERMISSION_DENIED => raise PermissionDenied
    STATUS_BACKEND_FAILURE => raise BackendFailure(message)
    _ => raise BackendFailure(message)
  }
}

///|
async fn read_text_async_impl() -> String raise ClipboardError {
  suspend((resume_ok, resume_err) => {
    js_read_text(resume_ok, (status, message) => {
      try {
        raise_for_status(status, message)
        resume_ok("")
      } catch {
        err => resume_err(err)
      }
    })
  })
}

///|
async fn write_text_async_impl(text : String) -> Unit raise ClipboardError {
  suspend((resume_ok, resume_err) => {
    js_write_text(text, () => resume_ok(()), (status, message) => {
      try {
        raise_for_status(status, message)
        resume_ok(())
      } catch {
        err => resume_err(err)
      }
    })
  })
}