/**
 * Internet schemas for wireguard configuration.
 *
 * @since 1.0.0
 */
import * as ParseResult from "@effect/schema/ParseResult";
import * as Schema from "@effect/schema/Schema";
import * as Brand from "effect/Brand";
import * as Duration from "effect/Duration";
import * as Function from "effect/Function";
import * as Option from "effect/Option";
import * as Predicate from "effect/Predicate";
import * as ReadonlyArray from "effect/ReadonlyArray";
import * as Stream from "effect/Stream";
import * as String from "effect/String";
import * as net from "node:net";
/**
 * @internal
 * @see {@link Split}
 */
export const splitLiteral = (str, delimiter) => str.split(delimiter);
/**
 * @since 1.0.0
 * @category Branded constructors
 */
export const DurationFromSecondsBrand = /*#__PURE__*/Brand.nominal();
/**
 * A schema that transforms a `number` tuple into a `Duration`. Treats the value
 * as the number of seconds.
 *
 * @since 1.0.0
 * @category Schemas
 */
export const DurationFromSeconds = /*#__PURE__*/Schema.transform(Schema.number, Schema.DurationFromSelf, ms => Duration.seconds(ms), duration => Duration.toSeconds(duration)).pipe(Schema.fromBrand(DurationFromSecondsBrand)).annotations({
  identifier: "DurationFromSeconds",
  description: "A duration from seconds"
});
/**
 * @since 1.0.0
 * @category Branded constructors
 */
export const PortBrand = /*#__PURE__*/Brand.nominal();
/**
 * An operating system port number.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { Port } from "the-wireguard-effect/InternetSchemas";
 *
 *     const port: Port = Port(8080);
 *     assert.strictEqual(port, 8080);
 *
 *     const decodePort = Schema.decodeSync(Port);
 *     assert.strictEqual(decodePort(8080), 8080);
 *
 *     assert.throws(() => decodePort(65536));
 *     assert.doesNotThrow(() => decodePort(8080));
 */
export const Port = /*#__PURE__*/Schema.Int.pipe(Schema.between(0, 2 ** 16 - 1)).pipe(Schema.fromBrand(PortBrand)).annotations({
  identifier: "Port",
  description: "A port number"
});
/**
 * An IPv4 address.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { IPv4 } from "the-wireguard-effect/InternetSchemas";
 *
 *     const ipv4: IPv4 = IPv4("1.1.1.1");
 *     assert.strictEqual(ipv4, "1.1.1.1");
 *
 *     const decodeIPv4 = Schema.decodeSync(IPv4);
 *     assert.strictEqual(decodeIPv4("1.1.1.1"), "1.1.1.1");
 *
 *     assert.throws(() => decodeIPv4("1.1.a.1"));
 *     assert.doesNotThrow(() => decodeIPv4("1.1.1.1"));
 */
export class IPv4 extends Schema.Class("IPv4")({
  ip: Schema.string.pipe(Schema.filter((s, _, ast) => net.isIPv4(s) ? Option.none() : Option.some(new ParseResult.Type(ast, s))))
}) {
  /** @since 1.0.0 */
  family = "ipv4";
  static FromBigint = n => {
    const padded = n.toString(16).replace(/:/g, "").padStart(8, "0");
    const groups = [];
    for (let i = 0; i < 8; i += 2) {
      const h = padded.slice(i, i + 2);
      groups.push(parseInt(h, 16));
    }
    return Schema.decode(IPv4)({
      ip: groups.join(".")
    });
  };
  get asBigint() {
    return Function.pipe(this.ip, String.split("."), ReadonlyArray.map(s => Number.parseInt(s, 10)), ReadonlyArray.map(n => n.toString(16)), ReadonlyArray.map(String.padStart(2, "0")), ReadonlyArray.join(""), hex => BigInt(`0x${hex}`));
  }
}
/**
 * A schema that transforms a `string` into an `IPv4`.
 *
 * @since 1.0.0
 * @category Schemas
 */
export const IPv4FromString = /*#__PURE__*/Schema.transform(Schema.string, IPv4, str => ({
  ip: str
}), ({
  ip
}) => ip).annotations({
  identifier: "IPv4FromString",
  description: "An ipv4 address"
});
/**
 * An IPv6 address.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { IPv6 } from "the-wireguard-effect/InternetSchemas";
 *
 *     const ipv6: IPv6 = IPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
 *     assert.strictEqual(ipv6, "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
 *
 *     const decodeIPv6 = Schema.decodeSync(IPv6);
 *     assert.deepStrictEqual(
 *         decodeIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
 *         "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
 *     );
 *
 *     assert.throws(() =>
 *         decodeIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334:")
 *     );
 *     assert.throws(() => decodeIPv6("2001::85a3::0000::0370:7334"));
 *     assert.doesNotThrow(() =>
 *         decodeIPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
 *     );
 */
export class IPv6 extends Schema.Class("IPv6")({
  ip: Schema.string.pipe(Schema.filter((s, _, ast) => net.isIPv6(s) ? Option.none() : Option.some(new ParseResult.Type(ast, s))))
}) {
  /** @since 1.0.0 */
  family = "ipv6";
  static FromBigint = n => {
    const hex = n.toString(16).padStart(32, "0");
    const groups = [];
    for (let i = 0; i < 8; i++) {
      groups.push(hex.slice(i * 4, (i + 1) * 4));
    }
    return Schema.decode(IPv6)({
      ip: groups.join(":")
    });
  };
  get asBigint() {
    function paddedHex(octet) {
      return parseInt(octet, 16).toString(16).padStart(4, "0");
    }
    let groups = [];
    const halves = this.ip.split("::");
    if (halves.length === 2) {
      let first = halves[0].split(":");
      let last = halves[1].split(":");
      if (first.length === 1 && first[0] === "") {
        first = [];
      }
      if (last.length === 1 && last[0] === "") {
        last = [];
      }
      const remaining = 8 - (first.length + last.length);
      if (!remaining) {
        // return Effect.fail(new ParseResult.Type(ast, "Error parsing groups"));
      }
      groups = groups.concat(first);
      for (let i = 0; i < remaining; i++) {
        groups.push("0");
      }
      groups = groups.concat(last);
    } else if (halves.length === 1) {
      groups = this.ip.split(":");
    } else {
      // return Effect.fail(new ParseResult.Type(ast, "Too many :: groups found"));
    }
    groups = groups.map(group => parseInt(group, 16).toString(16));
    if (groups.length !== 8) {
      // return Effect.fail(new ParseResult.Type(ast, "Invalid number of groups"));
    }
    return BigInt(`0x${groups.map(paddedHex).join("")}`);
  }
}
/**
 * A schema that transforms a `string` into an `IPv6`.
 *
 * @since 1.0.0
 * @category Schemas
 */
export const IPv6FromString = /*#__PURE__*/Schema.transform(Schema.string, IPv6, str => ({
  ip: str
}), ({
  ip
}) => ip).annotations({
  identifier: "IPv6FromString",
  description: "An ipv6 address"
});
/**
 * An IP address, which is either an IPv4 or IPv6 address.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { Address } from "the-wireguard-effect/InternetSchemas";
 *
 *     const address1: Address = Address("1.1.1.1");
 *     const address2: Address = Address(
 *         "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
 *     );
 *
 *     const decodeAddress = Schema.decodeSync(Address);
 *     assert.throws(() => decodeAddress("1.1.b.1"));
 *     assert.throws(() =>
 *         decodeAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334:")
 *     );
 *     assert.doesNotThrow(() => decodeAddress("1.1.1.2"));
 *     assert.doesNotThrow(() =>
 *         decodeAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
 *     );
 *
 * @see {@link IPv4}
 * @see {@link IPv6}
 */
export const Address = /*#__PURE__*/Schema.union(IPv4, IPv6).annotations({
  identifier: "Address",
  description: "An ip address"
});
/**
 * An IP address, which is either an IPv4 or IPv6 address. This schema
 * transforms a `string` into an `Address`.
 *
 * @since 1.0.0
 * @category Schemas
 */
export const AddressFromString = /*#__PURE__*/Schema.union(IPv4FromString, IPv6FromString).annotations({
  identifier: "AddressFromString",
  description: "An ip address"
});
/**
 * @since 1.0.0
 * @category Branded constructors
 */
export const IPv4CidrMaskBrand = /*#__PURE__*/Brand.nominal();
/**
 * An ipv4 cidr mask, which is a number between 0 and 32.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { IPv4CidrMask } from "the-wireguard-effect/InternetSchemas";
 *
 *     const mask: IPv4CidrMask = IPv4CidrMask(24);
 *     assert.strictEqual(mask, 24);
 *
 *     const decodeMask = Schema.decodeSync(IPv4CidrMask);
 *     assert.strictEqual(decodeMask(24), 24);
 *
 *     assert.throws(() => IPv4CidrMask(33));
 *     assert.doesNotThrow(() => IPv4CidrMask(0));
 *     assert.doesNotThrow(() => IPv4CidrMask(32));
 */
export const IPv4CidrMask = /*#__PURE__*/Schema.Int.pipe(Schema.between(0, 32)).pipe(Schema.fromBrand(IPv4CidrMaskBrand)).annotations({
  identifier: "IPv4CidrMask",
  description: "An ipv4 cidr mask"
});
/**
 * @since 1.0.0
 * @category Branded constructors
 */
export const IPv6CidrMaskBrand = /*#__PURE__*/Brand.nominal();
/**
 * An ipv6 cidr mask, which is a number between 0 and 128.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *     import { IPv6CidrMask } from "the-wireguard-effect/InternetSchemas";
 *
 *     const mask: IPv6CidrMask = IPv6CidrMask(64);
 *     assert.strictEqual(mask, 64);
 *
 *     const decodeMask = Schema.decodeSync(IPv6CidrMask);
 *     assert.strictEqual(decodeMask(64), 64);
 *
 *     assert.throws(() => IPv6CidrMask(129));
 *     assert.doesNotThrow(() => IPv6CidrMask(0));
 *     assert.doesNotThrow(() => IPv6CidrMask(128));
 */
export const IPv6CidrMask = /*#__PURE__*/Schema.Int.pipe(Schema.between(0, 128)).pipe(Schema.fromBrand(IPv6CidrMaskBrand)).annotations({
  identifier: "IPv6CidrMask",
  description: "An ipv6 cidr mask"
});
/**
 * Internal helper representation of a cidr range so that we can do
 * transformations to and from it as we cannot represent a stream using
 *
 * @since 1.0.0
 * @category Schemas
 */
export class CidrBlock extends Schema.Class("CidrBlock")({
  /** The IP address of this cidr block. */
  ip: AddressFromString,
  /** The subnet mask of this cidr block. */
  mask: Schema.union(IPv4CidrMask, IPv6CidrMask)
}) {
  /**
   * The address family of this cidr block.
   *
   * @since 1.0.0
   */
  family = this.ip.family;
  /**
   * The first address in the range given by this address' subnet, often
   * referred to as the Network Address.
   */
  get networkAddressAsBigint() {
    const bits = this.family === "ipv4" ? 32 : 128;
    const bigIntegerAddress = this.ip.asBigint;
    const intermediate = bigIntegerAddress.toString(2).padStart(bits, "0").slice(0, this.mask);
    const networkAddressString = intermediate + "0".repeat(bits - this.mask);
    const networkAddressBigInt = BigInt(`0b${networkAddressString}`);
    return networkAddressBigInt;
  }
  /**
   * The last address in the range given by this address' subnet, often
   * referred to as the Broadcast Address.
   */
  get broadcastAddressAsBigint() {
    const bits = this.family === "ipv4" ? 32 : 128;
    const bigIntegerAddress = this.ip.asBigint;
    const intermediate = bigIntegerAddress.toString(2).padStart(bits, "0").slice(0, this.mask);
    const broadcastAddressString = intermediate + "1".repeat(bits - this.mask);
    const broadcastAddressBigInt = BigInt(`0b${broadcastAddressString}`);
    return broadcastAddressBigInt;
  }
  /**
   * A stream of all addresses in the range given by this address' subnet.
   *
   * @since 1.0.0
   */
  get range() {
    const minValue = this.networkAddressAsBigint;
    const maxValue = this.broadcastAddressAsBigint;
    const addressFromBigint = this.family === "ipv4" ? IPv4.FromBigint : IPv6.FromBigint;
    const stream = Function.pipe(Stream.iterate(minValue, x => x + 1n), Stream.takeWhile(n => n <= maxValue), Stream.mapEffect(addressFromBigint));
    return stream;
  }
  get total() {
    const minValue = this.networkAddressAsBigint;
    const maxValue = this.broadcastAddressAsBigint;
    return maxValue - minValue + 1n;
  }
}
/**
 * A schema that transforms a `string` into a `CidrBlock`.
 *
 * @since 1.0.0
 * @category Schemas
 */
export const CidrBlockFromString = /*#__PURE__*/Schema.transform(Schema.templateLiteral(Schema.string, Schema.literal("/"), Schema.number), CidrBlock, str => {
  const [ip, mask] = splitLiteral(str, "/");
  return {
    ip,
    mask: Number.parseInt(mask, 10)
  };
}, ({
  ip,
  mask
}) => `${ip}/${mask}`).annotations({
  identifier: "CidrBlockFromString",
  description: "A cidr block"
});
/**
 * An IPv4 wireguard endpoint, which consists of an IPv4 address followed by a
 * nat port then an optional local port. If only one port is provided, it is
 * assumed that the nat port and listen port are the same.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *
 *     import {
 *         Port,
 *         IPv4,
 *         IPv4Endpoint,
 *     } from "the-wireguard-effect/InternetSchemas";
 *
 *     const endpoint1 = Schema.decodeSync(IPv4Endpoint)("1.2.3.4:51820");
 *     const endpoint2 = Schema.decodeSync(IPv4Endpoint)("1.2.3.4:51820:41820");
 *
 *     const endpoint3 = Schema.decodeSync(IPv4Endpoint)({
 *         ip: "1.2.3.4",
 *         port: 51820,
 *     });
 *
 *     const endpoint4: IPv4Endpoint = IPv4Endpoint({
 *         ip: IPv4("1.2.3.4"),
 *         natPort: Port(51820),
 *         listenPort: Port(41820),
 *     });
 */
export const IPv4Endpoint = /*#__PURE__*/Schema.transform(Schema.union(Schema.struct({
  ip: Schema.string,
  port: Schema.number
}), Schema.struct({
  ip: Schema.string,
  natPort: Schema.number,
  listenPort: Schema.number
}), Schema.templateLiteral(Schema.string, Schema.literal(":"), Schema.number), Schema.templateLiteral(Schema.string, Schema.literal(":"), Schema.number, Schema.literal(":"), Schema.number)), Schema.struct({
  address: IPv4FromString,
  natPort: Port,
  listenPort: Port
}), data => {
  const isObjectInput = !Predicate.isString(data);
  const [ip, natPort, listenPort] = isObjectInput ? [data.ip, "natPort" in data ? `${data.natPort}` : `${data.port}`, "listenPort" in data ? `${data.listenPort}` : undefined] : splitLiteral(data, ":");
  const natPortParsed = Number.parseInt(natPort, 10);
  const listenPortParsed = Predicate.isNotUndefined(listenPort) ? Number.parseInt(listenPort, 10) : natPortParsed;
  return {
    address: ip,
    natPort: natPortParsed,
    listenPort: listenPortParsed
  };
}, ({
  address,
  natPort,
  listenPort
}) => `${address}:${natPort}:${listenPort}`).annotations({
  identifier: "IPv4Endpoint",
  description: "An ipv4 wireguard endpoint"
});
/**
 * An IPv6 wireguard endpoint, which consists of an IPv6 address in square
 * brackets followed by a nat port then an optional local port. If only one port
 * is provided, it is assumed that the nat port and listen port are the same.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *
 *     import {
 *         Port,
 *         IPv6,
 *         IPv6Endpoint,
 *     } from "the-wireguard-effect/InternetSchemas";
 *
 *     const endpoint1 = Schema.decodeSync(IPv6Endpoint)(
 *         "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820"
 *     );
 *     const endpoint2 = Schema.decodeSync(IPv6Endpoint)(
 *         "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820:41820"
 *     );
 *
 *     const endpoint3 = Schema.decodeSync(IPv6Endpoint)({
 *         ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
 *         port: 51820,
 *     });
 *
 *     const endpoint4: IPv6Endpoint = IPv6Endpoint({
 *         ip: IPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
 *         natPort: Port(51820),
 *         listenPort: Port(41820),
 *     });
 */
export const IPv6Endpoint = /*#__PURE__*/Schema.transform(Schema.union(Schema.struct({
  ip: Schema.string,
  port: Schema.number
}), Schema.struct({
  ip: Schema.string,
  natPort: Schema.number,
  listenPort: Schema.number
}), Schema.templateLiteral(Schema.literal("["), Schema.string, Schema.literal("]"), Schema.literal(":"), Schema.number), Schema.templateLiteral(Schema.literal("["), Schema.string, Schema.literal("]"), Schema.literal(":"), Schema.number, Schema.literal(":"), Schema.number)), Schema.struct({
  address: IPv6FromString,
  natPort: Port,
  listenPort: Port
}), data => {
  const isObjectInput = !Predicate.isString(data);
  const tail = elements => elements.slice(1);
  const [ip, natPort, listenPort] = isObjectInput ? [data.ip, "natPort" in data ? `${data.natPort}` : `${data.port}`, "listenPort" in data ? `${data.listenPort}` : undefined] : [splitLiteral(data, "]")[0].slice(1), ...tail(splitLiteral(splitLiteral(data, "]")[1], ":"))];
  const natPortParsed = Number.parseInt(natPort, 10);
  const listenPortParsed = Predicate.isNotUndefined(listenPort) ? Number.parseInt(listenPort, 10) : natPortParsed;
  return {
    address: ip.slice(1),
    natPort: natPortParsed,
    listenPort: listenPortParsed
  };
}, ({
  address,
  natPort,
  listenPort
}) => `[${address}]:${natPort}:${listenPort}`).annotations({
  identifier: "IPv6Endpoint",
  description: "An ipv6 wireguard endpoint"
});
/**
 * A wireguard endpoint, which is either an IPv4 or IPv6 endpoint.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *
 *     import {
 *         Endpoint,
 *         IPv4,
 *         IPv6,
 *         Port,
 *     } from "the-wireguard-effect/InternetSchemas";
 *
 *     const endpoint1 = Schema.decodeSync(Endpoint)("1.2.3.4:51820");
 *     const endpoint2 = Schema.decodeSync(Endpoint)("1.2.3.4:51820:41820");
 *
 *     const endpoint3 = Schema.decodeSync(Endpoint)({
 *         ip: "1.2.3.4",
 *         port: 51820,
 *     });
 *
 *     const endpoint4: Endpoint = Endpoint({
 *         ip: IPv4("1.2.3.4"),
 *         natPort: Port(51820),
 *         listenPort: Port(41820),
 *     });
 *
 *     const endpoint5 = Schema.decodeSync(Endpoint)(
 *         "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820"
 *     );
 *     const endpoint6 = Schema.decodeSync(Endpoint)(
 *         "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:51820:41820"
 *     );
 *
 *     const endpoint7 = Schema.decodeSync(Endpoint)({
 *         ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
 *         port: 51820,
 *     });
 *
 *     const endpoint8: Endpoint = Endpoint({
 *         ip: IPv6("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
 *         natPort: Port(51820),
 *         listenPort: Port(41820),
 *     });
 *
 * @see {@link IPv4Endpoint}
 * @see {@link IPv6Endpoint}
 */
export const Endpoint = /*#__PURE__*/Schema.union(IPv4Endpoint, IPv6Endpoint).annotations({
  identifier: "Endpoint",
  description: "An ipv4 or ipv6 wireguard endpoint"
});
/**
 * A wireguard setup data, which consists of an endpoint followed by an address.
 *
 * @since 1.0.0
 * @category Schemas
 * @example
 *     import * as Schema from "@effect/schema/Schema";
 *
 *     import {
 *         Address,
 *         Endpoint,
 *         SetupData,
 *     } from "the-wireguard-effect/InternetSchemas";
 *
 *     const setupData1 = Schema.decodeSync(SetupData)([
 *         "1.1.1.1:51280",
 *         "10.0.0.1",
 *     ]);
 *
 *     const address = Schema.decodeSync(Address)("10.0.0.1");
 *     const endpoint = Schema.decodeSync(Endpoint)("1.1.1.1:51820");
 *     const setupData2: SetupData = SetupData([endpoint, address]);
 *
 * @see {@link EndpointSchema}
 * @see {@link Address}
 */
export const SetupData = /*#__PURE__*/Schema.tuple(Endpoint, AddressFromString).annotations({
  identifier: "SetupData",
  description: "A wireguard setup data"
});
//# sourceMappingURL=InternetSchemas.js.map