import { isIPv4 } from '@chainsafe/is-ip';
import { base32 } from 'multiformats/bases/base32';
import { bases } from 'multiformats/basics';
import { concat as uint8ArrayConcat } from 'uint8arrays/concat';
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string';
import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
import { InvalidMultiaddrError } from "./errors.js";
export function bytesToString(base) {
    return (buf) => {
        return uint8ArrayToString(buf, base);
    };
}
export function stringToBytes(base) {
    return (buf) => {
        return uint8ArrayFromString(buf, base);
    };
}
export function bytes2port(buf) {
    const view = new DataView(buf.buffer);
    return view.getUint16(buf.byteOffset).toString();
}
export function port2bytes(port) {
    const buf = new ArrayBuffer(2);
    const view = new DataView(buf);
    view.setUint16(0, typeof port === 'string' ? parseInt(port) : port);
    return new Uint8Array(buf);
}
export function onion2bytes(str) {
    const addr = str.split(':');
    if (addr.length !== 2) {
        throw new Error(`failed to parse onion addr: ["'${addr.join('", "')}'"]' does not contain a port number`);
    }
    if (addr[0].length !== 16) {
        throw new Error(`failed to parse onion addr: ${addr[0]} not a Tor onion address.`);
    }
    // onion addresses do not include the multibase prefix, add it before decoding
    const buf = uint8ArrayFromString(addr[0], 'base32');
    // onion port number
    const port = parseInt(addr[1], 10);
    if (port < 1 || port > 65536) {
        throw new Error('Port number is not in range(1, 65536)');
    }
    const portBuf = port2bytes(port);
    return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length);
}
export function onion32bytes(str) {
    const addr = str.split(':');
    if (addr.length !== 2) {
        throw new Error(`failed to parse onion addr: ["'${addr.join('", "')}'"]' does not contain a port number`);
    }
    if (addr[0].length !== 56) {
        throw new Error(`failed to parse onion addr: ${addr[0]} not a Tor onion3 address.`);
    }
    // onion addresses do not include the multibase prefix, add it before decoding
    const buf = base32.decode(`b${addr[0]}`);
    // onion port number
    const port = parseInt(addr[1], 10);
    if (port < 1 || port > 65536) {
        throw new Error('Port number is not in range(1, 65536)');
    }
    const portBuf = port2bytes(port);
    return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length);
}
export function bytes2onion(buf) {
    const addrBytes = buf.subarray(0, buf.length - 2);
    const portBytes = buf.subarray(buf.length - 2);
    const addr = uint8ArrayToString(addrBytes, 'base32');
    const port = bytes2port(portBytes);
    return `${addr}:${port}`;
}
// Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
// but with buf/offset args removed because we don't use them
export const ip4ToBytes = function (ip) {
    ip = ip.toString().trim();
    const bytes = new Uint8Array(4);
    ip.split(/\./g).forEach((byte, index) => {
        const value = parseInt(byte, 10);
        if (isNaN(value) || value < 0 || value > 0xff) {
            throw new InvalidMultiaddrError('Invalid byte value in IP address');
        }
        bytes[index] = value;
    });
    return bytes;
};
// Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
// but with buf/offset args removed because we don't use them
export const ip6ToBytes = function (ip) {
    let offset = 0;
    ip = ip.toString().trim();
    const sections = ip.split(':', 8);
    let i;
    for (i = 0; i < sections.length; i++) {
        const isv4 = isIPv4(sections[i]);
        let v4Buffer;
        if (isv4) {
            v4Buffer = ip4ToBytes(sections[i]);
            sections[i] = uint8ArrayToString(v4Buffer.subarray(0, 2), 'base16');
        }
        if (v4Buffer != null && ++i < 8) {
            sections.splice(i, 0, uint8ArrayToString(v4Buffer.subarray(2, 4), 'base16'));
        }
    }
    if (sections[0] === '') {
        while (sections.length < 8) {
            sections.unshift('0');
        }
    }
    else if (sections[sections.length - 1] === '') {
        while (sections.length < 8) {
            sections.push('0');
        }
    }
    else if (sections.length < 8) {
        for (i = 0; i < sections.length && sections[i] !== ''; i++) { }
        const argv = [i, 1];
        for (i = 9 - sections.length; i > 0; i--) {
            argv.push('0');
        }
        sections.splice.apply(sections, argv);
    }
    const bytes = new Uint8Array(offset + 16);
    for (i = 0; i < sections.length; i++) {
        if (sections[i] === '') {
            sections[i] = '0';
        }
        const word = parseInt(sections[i], 16);
        if (isNaN(word) || word < 0 || word > 0xffff) {
            throw new InvalidMultiaddrError('Invalid byte value in IP address');
        }
        bytes[offset++] = (word >> 8) & 0xff;
        bytes[offset++] = word & 0xff;
    }
    return bytes;
};
// Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
export const ip4ToString = function (buf) {
    if (buf.byteLength !== 4) {
        throw new InvalidMultiaddrError('IPv4 address was incorrect length');
    }
    const result = [];
    for (let i = 0; i < buf.byteLength; i++) {
        result.push(buf[i]);
    }
    return result.join('.');
};
export const ip6ToString = function (buf) {
    if (buf.byteLength !== 16) {
        throw new InvalidMultiaddrError('IPv6 address was incorrect length');
    }
    const result = [];
    for (let i = 0; i < buf.byteLength; i += 2) {
        const byte1 = buf[i];
        const byte2 = buf[i + 1];
        const tuple = `${byte1.toString(16).padStart(2, '0')}${byte2.toString(16).padStart(2, '0')}`;
        result.push(tuple);
    }
    const ip = result.join(':');
    try {
        const url = new URL(`http://[${ip}]`);
        return url.hostname.substring(1, url.hostname.length - 1);
    }
    catch {
        throw new InvalidMultiaddrError(`Invalid IPv6 address "${ip}"`);
    }
};
export function ip6StringToValue(str) {
    try {
        const url = new URL(`http://[${str}]`);
        return url.hostname.substring(1, url.hostname.length - 1);
    }
    catch {
        throw new InvalidMultiaddrError(`Invalid IPv6 address "${str}"`);
    }
}
const decoders = Object.values(bases).map((c) => c.decoder);
const anybaseDecoder = (function () {
    let acc = decoders[0].or(decoders[1]);
    decoders.slice(2).forEach((d) => (acc = acc.or(d)));
    return acc;
})();
export function mb2bytes(mbstr) {
    return anybaseDecoder.decode(mbstr);
}
export function bytes2mb(base) {
    return (buf) => {
        return base.encoder.encode(buf);
    };
}
//# sourceMappingURL=utils.js.map