import {
  bytes16,
  bytes24,
  bytes32,
  bytesF32,
  bytesF64
} from "@thi.ng/binary/bytes";
import { unsupported } from "@thi.ng/errors/unsupported";
import { iterator } from "@thi.ng/transducers/iterator";
import { mapcat } from "@thi.ng/transducers/mapcat";
import { reduce } from "@thi.ng/transducers/reduce";
import { utf8Encode } from "./utf8.js";
const i8 = (x) => ["i8", x];
const i8array = (x) => ["i8a", x];
const u8 = (x) => ["u8", x];
const u8array = (x) => ["u8a", x];
const i16 = (x, le = false) => ["i16", x, le];
const i16array = (x, le = false) => [
  "i16a",
  x,
  le
];
const u16 = (x, le = false) => ["u16", x, le];
const u16array = (x, le = false) => [
  "u16a",
  x,
  le
];
const i24 = (x, le = false) => ["i24", x, le];
const i24array = (x, le = false) => [
  "i24a",
  x,
  le
];
const u24 = (x, le = false) => ["u24", x, le];
const u24array = (x, le = false) => [
  "u24a",
  x,
  le
];
const i32 = (x, le = false) => ["i32", x, le];
const i32array = (x, le = false) => [
  "i32a",
  x,
  le
];
const u32 = (x, le = false) => ["u32", x, le];
const u32array = (x, le = false) => [
  "u32a",
  x,
  le
];
const f32 = (x, le = false) => ["f32", x, le];
const f32array = (x, le = false) => [
  "f32a",
  x,
  le
];
const f64 = (x, le = false) => ["f64", x, le];
const f64array = (x, le = false) => [
  "f64a",
  x,
  le
];
const str = (x) => ["str", x];
function asBytes(src) {
  return src ? iterator(asBytes(), src) : mapcat((x) => {
    const val = x[1];
    const le = x[2];
    switch (x[0]) {
      case "i8":
      case "u8":
        return [val];
      case "i8a":
      case "u8a":
        return x[1];
      case "i16":
      case "u16":
        return bytes16(val, le);
      case "i16a":
      case "u16a":
        return mapcat((x2) => bytes16(x2, le), x[1]);
      case "i24":
      case "u24":
        return bytes24(val, le);
      case "i24a":
      case "u24a":
        return mapcat((x2) => bytes24(x2, le), x[1]);
      case "i32":
      case "u32":
        return bytes32(val, le);
      case "i32a":
      case "u32a":
        return mapcat((x2) => bytes32(x2, le), x[1]);
      case "f32":
        return bytesF32(val, le);
      case "f32a":
        return mapcat((x2) => bytesF32(x2, le), x[1]);
      case "f64":
        return bytesF64(val, le);
      case "f64a":
        return mapcat((x2) => bytesF64(x2, le), x[1]);
      case "str":
        return utf8Encode(x[1]);
      default:
        unsupported(`invalid struct item: ${x[0]}`);
    }
  });
}
function bytes(cap = 1024, src) {
  let view;
  let pos = 0;
  const ensure = (acc, size) => {
    if (pos + size <= cap) return acc;
    cap *= 2;
    const buf = new Uint8Array(cap);
    buf.set(acc);
    view = new DataView(buf.buffer);
    return buf;
  };
  const setArray = (fn, stride, acc, x, le) => {
    const n = x.length;
    acc = ensure(acc, stride * n);
    for (let i = 0; i < n; i++, pos += stride) {
      view[fn](pos, x[i], le);
    }
    return acc;
  };
  return src ? reduce(bytes(cap), src) : [
    () => new Uint8Array(cap),
    (acc) => acc.subarray(0, pos),
    (acc, [type, x, le = false]) => {
      if (!view || view.buffer !== acc.buffer) {
        cap = acc.byteLength;
        view = new DataView(acc.buffer, acc.byteOffset);
      }
      switch (type) {
        case "i8":
          acc = ensure(acc, 1);
          view.setInt8(pos, x);
          pos++;
          break;
        case "i8a": {
          const n = x.length;
          acc = ensure(acc, n);
          new Int8Array(acc.buffer, acc.byteOffset).set(
            x,
            pos
          );
          pos += n;
          break;
        }
        case "u8":
          acc = ensure(acc, 1);
          view.setUint8(pos, x);
          pos++;
          break;
        case "u8a": {
          const n = x.length;
          acc = ensure(acc, n);
          acc.set(x, pos);
          pos += n;
          break;
        }
        case "i16":
          acc = ensure(acc, 2);
          view.setInt16(pos, x, le);
          pos += 2;
          break;
        case "i16a":
          acc = setArray("setInt16", 2, acc, x, le);
          break;
        case "u16":
          acc = ensure(acc, 2);
          view.setUint16(pos, x, le);
          pos += 2;
          break;
        case "u16a":
          acc = setArray("setUint16", 2, acc, x, le);
          break;
        case "i24":
          acc = ensure(acc, 4);
          view.setInt32(pos, x, le);
          pos += 3;
          break;
        case "i24a":
          acc = setArray("setInt32", 3, acc, x, le);
          break;
        case "u24":
          acc = ensure(acc, 4);
          view.setUint32(pos, x, le);
          pos += 3;
          break;
        case "u24a":
          acc = setArray("setUint32", 3, acc, x, le);
          break;
        case "i32":
          acc = ensure(acc, 4);
          view.setInt32(pos, x, le);
          pos += 4;
          break;
        case "i32a":
          acc = setArray("setInt32", 4, acc, x, le);
          break;
        case "u32":
          acc = ensure(acc, 4);
          view.setUint32(pos, x, le);
          pos += 4;
          break;
        case "u32a":
          acc = setArray("setUint32", 4, acc, x, le);
          break;
        case "f32":
          acc = ensure(acc, 4);
          view.setFloat32(pos, x, le);
          pos += 4;
          break;
        case "f32a":
          acc = setArray("setFloat32", 4, acc, x, le);
          break;
        case "f64":
          acc = ensure(acc, 8);
          view.setFloat64(pos, x, le);
          pos += 8;
          break;
        case "f64a":
          acc = setArray("setFloat64", 8, acc, x, le);
          break;
        case "str": {
          let utf = utf8Encode(x);
          acc = ensure(acc, utf.length);
          acc.set(utf, pos);
          pos += utf.length;
          break;
        }
        default:
      }
      return acc;
    }
  ];
}
export {
  asBytes,
  bytes,
  f32,
  f32array,
  f64,
  f64array,
  i16,
  i16array,
  i24,
  i24array,
  i32,
  i32array,
  i8,
  i8array,
  str,
  u16,
  u16array,
  u24,
  u24array,
  u32,
  u32array,
  u8,
  u8array
};
