// cspell: ignore golomb
// cspell: ignore qpprime
// cspell: ignore colour
// cspell: ignore inbld
// cspell: ignore Coef
// cspell: ignore coeffs
// cspell: ignore Pocs
// cspell: ignore overscan
// cspell: ignore disp
// cspell: ignore bitstream
// cspell: ignore scal
// cspell: ignore ivmc
// cspell: ignore dbbp
// cspell: ignore texmc
// cspell: ignore rbsp
// cspell: ignore cprms
// cspell: ignore nalu
// cspell: ignore sodb
// cspell: ignore luma

import { NaluSodbBitReader, annexBSplitNalu } from "./nalu.js";

export enum AndroidHevcProfile {
    Main = 1 << 0,
    Main10 = 1 << 1,
    MainStill = 1 << 2,
    Main10Hdr10 = 1 << 12,
    Main10Hdr10Plus = 1 << 13,
}

export enum AndroidHevcLevel {
    MainTierLevel1 = 1 << 0,
    HighTierLevel1 = 1 << 1,
    MainTierLevel2 = 1 << 2,
    HighTierLevel2 = 1 << 3,
    MainTierLevel21 = 1 << 4,
    HighTierLevel21 = 1 << 5,
    MainTierLevel3 = 1 << 6,
    HighTierLevel3 = 1 << 7,
    MainTierLevel31 = 1 << 8,
    HighTierLevel31 = 1 << 9,
    MainTierLevel4 = 1 << 10,
    HighTierLevel4 = 1 << 11,
    MainTierLevel41 = 1 << 12,
    HighTierLevel41 = 1 << 13,
    MainTierLevel5 = 1 << 14,
    HighTierLevel5 = 1 << 15,
    MainTierLevel51 = 1 << 16,
    HighTierLevel51 = 1 << 17,
    MainTierLevel52 = 1 << 18,
    HighTierLevel52 = 1 << 19,
    MainTierLevel6 = 1 << 20,
    HighTierLevel6 = 1 << 21,
    MainTierLevel61 = 1 << 22,
    HighTierLevel61 = 1 << 23,
    MainTierLevel62 = 1 << 24,
    HighTierLevel62 = 1 << 25,
}

/**
 * 6.2 Source, decoded and output picture formats
 */
export function getSubWidthC(chroma_format_idc: number) {
    switch (chroma_format_idc) {
        case 0:
        case 3:
            return 1;
        case 1:
        case 2:
            return 2;
        default:
            throw new Error("Invalid chroma_format_idc");
    }
}

/**
 * 6.2 Source, decoded and output picture formats
 */
export function getSubHeightC(chroma_format_idc: number) {
    switch (chroma_format_idc) {
        case 0:
        case 2:
        case 3:
            return 1;
        case 1:
            return 2;
        default:
            throw new Error("Invalid chroma_format_idc");
    }
}

/**
 * 7.3.1.1 General NAL unit syntax
 */
export function h265ParseNaluHeader(nalu: Uint8Array) {
    const reader = new NaluSodbBitReader(nalu);
    if (reader.next() !== 0) {
        throw new Error("Invalid NALU header");
    }

    const nal_unit_type = reader.read(6);
    const nuh_layer_id = reader.read(6);
    const nuh_temporal_id_plus1 = reader.read(3);

    return {
        nal_unit_type,
        nuh_layer_id,
        nuh_temporal_id_plus1,
    };
}

export type H265NaluHeader = ReturnType<typeof h265ParseNaluHeader>;

export interface H265NaluRaw extends H265NaluHeader {
    data: Uint8Array;
    rbsp: Uint8Array;
}

/**
 * 7.3.2.1 Video parameter set RBSP syntax
 */
export function h265ParseVideoParameterSet(nalu: Uint8Array) {
    const reader = new NaluSodbBitReader(nalu);

    const vps_video_parameter_set_id = reader.read(4);
    const vps_base_layer_internal_flag = !!reader.next();
    const vps_base_layer_available_flag = !!reader.next();
    const vps_max_layers_minus1 = reader.read(6);
    const vps_max_sub_layers_minus1 = reader.read(3);
    const vps_temporal_id_nesting_flag = !!reader.next();
    reader.skip(16);

    const profileTierLevel = h265ParseProfileTierLevel(
        reader,
        true,
        vps_max_sub_layers_minus1,
    );

    const vps_sub_layer_ordering_info_present_flag = !!reader.next();
    const vps_max_dec_pic_buffering_minus1: number[] = [];
    const vps_max_num_reorder_pics: number[] = [];
    const vps_max_latency_increase_plus1: number[] = [];
    for (
        let i = vps_sub_layer_ordering_info_present_flag
            ? 0
            : vps_max_sub_layers_minus1;
        i <= vps_max_sub_layers_minus1;
        i += 1
    ) {
        vps_max_dec_pic_buffering_minus1[i] =
            reader.decodeExponentialGolombNumber();
        vps_max_num_reorder_pics[i] = reader.decodeExponentialGolombNumber();
        vps_max_latency_increase_plus1[i] =
            reader.decodeExponentialGolombNumber();
    }

    const vps_max_layer_id = reader.read(6);
    const vps_num_layer_sets_minus1 = reader.decodeExponentialGolombNumber();
    const layer_id_included_flag: boolean[][] = [];
    for (let i = 1; i <= vps_num_layer_sets_minus1; i += 1) {
        layer_id_included_flag[i] = [];
        for (let j = 0; j <= vps_max_layer_id; j += 1) {
            layer_id_included_flag[i]![j] = !!reader.next();
        }
    }

    const vps_timing_info_present_flag = !!reader.next();
    let vps_num_units_in_tick: number | undefined;
    let vps_time_scale: number | undefined;
    let vps_poc_proportional_to_timing_flag: boolean | undefined;
    let vps_num_ticks_poc_diff_one_minus1: number | undefined;
    let vps_num_hrd_parameters: number | undefined;
    let hrd_layer_set_idx: number[] | undefined;
    let cprms_present_flag: boolean[] | undefined;
    let hrdParameters: H265HrdParameters[] | undefined;
    if (vps_timing_info_present_flag) {
        vps_num_units_in_tick = reader.read(32);
        vps_time_scale = reader.read(32);

        vps_poc_proportional_to_timing_flag = !!reader.next();
        if (vps_poc_proportional_to_timing_flag) {
            vps_num_ticks_poc_diff_one_minus1 =
                reader.decodeExponentialGolombNumber();
        }

        vps_num_hrd_parameters = reader.decodeExponentialGolombNumber();

        hrd_layer_set_idx = [];
        cprms_present_flag = [true];
        hrdParameters = [];
        for (let i = 0; i < vps_num_hrd_parameters; i += 1) {
            hrd_layer_set_idx[i] = reader.decodeExponentialGolombNumber();
            if (i > 0) {
                cprms_present_flag[i] = !!reader.next();
            }
            hrdParameters[i] = h265ParseHrdParameters(
                reader,
                cprms_present_flag[i]!,
                vps_max_sub_layers_minus1,
            );
        }
    }

    const vps_extension_flag = !!reader.next();

    return {
        vps_video_parameter_set_id,
        vps_base_layer_internal_flag,
        vps_base_layer_available_flag,
        vps_max_layers_minus1,
        vps_max_sub_layers_minus1,
        vps_temporal_id_nesting_flag,
        profileTierLevel,
        vps_sub_layer_ordering_info_present_flag,
        vps_max_dec_pic_buffering_minus1,
        vps_max_num_reorder_pics,
        vps_max_latency_increase_plus1,
        vps_max_layer_id,
        vps_num_layer_sets_minus1,
        layer_id_included_flag,
        vps_timing_info_present_flag,
        vps_num_units_in_tick,
        vps_time_scale,
        vps_poc_proportional_to_timing_flag,
        vps_num_ticks_poc_diff_one_minus1,
        vps_num_hrd_parameters,
        hrd_layer_set_idx,
        cprms_present_flag,
        hrdParameters,
        vps_extension_flag,
    };
}

export type SubLayerHrdParameters = ReturnType<
    typeof h265ParseSubLayerHrdParameters
>;

/**
 * 7.3.2.2.1 General sequence parameter set RBSP syntax
 */
export function h265ParseSequenceParameterSet(nalu: Uint8Array) {
    const reader = new NaluSodbBitReader(nalu);

    const sps_video_parameter_set_id = reader.read(4);
    const sps_max_sub_layers_minus1 = reader.read(3);
    const sps_temporal_id_nesting_flag = !!reader.next();

    const profileTierLevel = h265ParseProfileTierLevel(
        reader,
        true,
        sps_max_sub_layers_minus1,
    );

    const sps_seq_parameter_set_id = reader.decodeExponentialGolombNumber();
    const chroma_format_idc = reader.decodeExponentialGolombNumber();
    let separate_colour_plane_flag: boolean | undefined;
    if (chroma_format_idc === 3) {
        separate_colour_plane_flag = !!reader.next();
    }

    const pic_width_in_luma_samples = reader.decodeExponentialGolombNumber();
    const pic_height_in_luma_samples = reader.decodeExponentialGolombNumber();

    const conformance_window_flag = !!reader.next();
    let conf_win_left_offset: number | undefined;
    let conf_win_right_offset: number | undefined;
    let conf_win_top_offset: number | undefined;
    let conf_win_bottom_offset: number | undefined;
    if (conformance_window_flag) {
        conf_win_left_offset = reader.decodeExponentialGolombNumber();
        conf_win_right_offset = reader.decodeExponentialGolombNumber();
        conf_win_top_offset = reader.decodeExponentialGolombNumber();
        conf_win_bottom_offset = reader.decodeExponentialGolombNumber();
    }

    const bit_depth_luma_minus8 = reader.decodeExponentialGolombNumber();
    const bit_depth_chroma_minus8 = reader.decodeExponentialGolombNumber();
    const log2_max_pic_order_cnt_lsb_minus4 =
        reader.decodeExponentialGolombNumber();

    const sps_max_dec_pic_buffering_minus1: number[] = [];
    const sps_max_num_reorder_pics: number[] = [];
    const sps_max_latency_increase_plus1: number[] = [];
    const sps_sub_layer_ordering_info_present_flag = !!reader.next();
    for (
        let i = sps_sub_layer_ordering_info_present_flag
            ? 0
            : sps_max_sub_layers_minus1;
        i <= sps_max_sub_layers_minus1;
        i += 1
    ) {
        sps_max_dec_pic_buffering_minus1[i] =
            reader.decodeExponentialGolombNumber();
        sps_max_num_reorder_pics[i] = reader.decodeExponentialGolombNumber();
        sps_max_latency_increase_plus1[i] =
            reader.decodeExponentialGolombNumber();
    }

    const log2_min_luma_coding_block_size_minus3 =
        reader.decodeExponentialGolombNumber();
    const log2_diff_max_min_luma_coding_block_size =
        reader.decodeExponentialGolombNumber();
    const log2_min_luma_transform_block_size_minus2 =
        reader.decodeExponentialGolombNumber();
    const log2_diff_max_min_luma_transform_block_size =
        reader.decodeExponentialGolombNumber();
    const max_transform_hierarchy_depth_inter =
        reader.decodeExponentialGolombNumber();
    const max_transform_hierarchy_depth_intra =
        reader.decodeExponentialGolombNumber();

    const scaling_list_enabled_flag = !!reader.next();
    let sps_scaling_list_data_present_flag: boolean | undefined;
    let scalingListData: number[][][] | undefined;
    if (scaling_list_enabled_flag) {
        sps_scaling_list_data_present_flag = !!reader.next();
        if (sps_scaling_list_data_present_flag) {
            scalingListData = h265ParseScalingListData(reader);
        }
    }

    const amp_enabled_flag = !!reader.next();
    const sample_adaptive_offset_enabled_flag = !!reader.next();
    const pcm_enabled_flag = !!reader.next();
    let pcm_sample_bit_depth_luma_minus1: number | undefined;
    let pcm_sample_bit_depth_chroma_minus1: number | undefined;
    let log2_min_pcm_luma_coding_block_size_minus3: number | undefined;
    let log2_diff_max_min_pcm_luma_coding_block_size: number | undefined;
    let pcm_loop_filter_disabled_flag: boolean | undefined;
    if (pcm_enabled_flag) {
        pcm_sample_bit_depth_luma_minus1 = reader.read(4);
        pcm_sample_bit_depth_chroma_minus1 = reader.read(4);
        log2_min_pcm_luma_coding_block_size_minus3 = reader.read(4);
        log2_diff_max_min_pcm_luma_coding_block_size = reader.read(4);
        pcm_loop_filter_disabled_flag = !!reader.next();
    }

    const num_short_term_ref_pic_sets = reader.decodeExponentialGolombNumber();
    const shortTermRefPicSets: ShortTermReferencePictureSet[] = [];
    for (let i = 0; i < num_short_term_ref_pic_sets; i += 1) {
        shortTermRefPicSets[i] = h265ParseShortTermReferencePictureSet(
            reader,
            i,
            num_short_term_ref_pic_sets,
            shortTermRefPicSets,
        );
    }

    const long_term_ref_pics_present_flag = !!reader.next();
    let num_long_term_ref_pics_sps: number | undefined;
    let lt_ref_pic_poc_lsb_sps: number[] | undefined;
    let used_by_curr_pic_lt_sps_flag: boolean[] | undefined;
    if (long_term_ref_pics_present_flag) {
        num_long_term_ref_pics_sps = reader.decodeExponentialGolombNumber();
        lt_ref_pic_poc_lsb_sps = [];
        used_by_curr_pic_lt_sps_flag = [];
        for (let i = 0; i < num_long_term_ref_pics_sps; i += 1) {
            lt_ref_pic_poc_lsb_sps[i] = reader.read(
                log2_max_pic_order_cnt_lsb_minus4 + 4,
            );
            used_by_curr_pic_lt_sps_flag[i] = !!reader.next();
        }
    }

    const sps_temporal_mvp_enabled_flag = !!reader.next();
    const strong_intra_smoothing_enabled_flag = !!reader.next();
    const vui_parameters_present_flag = !!reader.next();
    let vuiParameters: H265VuiParameters | undefined;
    if (vui_parameters_present_flag) {
        vuiParameters = h265ParseVuiParameters(
            reader,
            sps_max_sub_layers_minus1,
        );
    }

    const sps_extension_present_flag = !!reader.next();
    let sps_range_extension_flag: boolean | undefined;
    let sps_multilayer_extension_flag: boolean | undefined;
    let sps_3d_extension_flag: boolean | undefined;
    let sps_scc_extension_flag: boolean | undefined;
    let sps_extension_4bits: number | undefined;
    if (sps_extension_present_flag) {
        sps_range_extension_flag = !!reader.next();
        sps_multilayer_extension_flag = !!reader.next();
        sps_3d_extension_flag = !!reader.next();
        sps_scc_extension_flag = !!reader.next();
        sps_extension_4bits = reader.read(4);
    }

    if (sps_range_extension_flag) {
        throw new Error("Not implemented");
    }

    let spsMultilayerExtension: H265SpsMultilayerExtension | undefined;
    if (sps_multilayer_extension_flag) {
        spsMultilayerExtension = h265ParseSpsMultilayerExtension(reader);
    }

    let sps3dExtension: H265Sps3dExtension | undefined;
    if (sps_3d_extension_flag) {
        sps3dExtension = h265ParseSps3dExtension(reader);
    }

    if (sps_scc_extension_flag) {
        throw new Error("Not implemented");
    }

    let sps_extension_data_flag: boolean[] | undefined;
    if (sps_extension_4bits) {
        sps_extension_data_flag = [];
        let i = 0;
        while (!reader.ended) {
            sps_extension_data_flag[i] = !!reader.next();
            i += 1;
        }
    }

    return {
        sps_video_parameter_set_id,
        sps_max_sub_layers_minus1,
        sps_temporal_id_nesting_flag,
        profileTierLevel,
        sps_seq_parameter_set_id,
        chroma_format_idc,
        separate_colour_plane_flag,
        pic_width_in_luma_samples,
        pic_height_in_luma_samples,
        conformance_window_flag,
        conf_win_left_offset,
        conf_win_right_offset,
        conf_win_top_offset,
        conf_win_bottom_offset,
        bit_depth_luma_minus8,
        bit_depth_chroma_minus8,
        log2_max_pic_order_cnt_lsb_minus4,
        sps_sub_layer_ordering_info_present_flag,
        sps_max_dec_pic_buffering_minus1,
        sps_max_num_reorder_pics,
        sps_max_latency_increase_plus1,
        log2_min_luma_coding_block_size_minus3,
        log2_diff_max_min_luma_coding_block_size,
        log2_min_luma_transform_block_size_minus2,
        log2_diff_max_min_luma_transform_block_size,
        max_transform_hierarchy_depth_inter,
        max_transform_hierarchy_depth_intra,
        scaling_list_enabled_flag,
        sps_scaling_list_data_present_flag,
        scalingListData,
        amp_enabled_flag,
        sample_adaptive_offset_enabled_flag,
        pcm_enabled_flag,
        pcm_sample_bit_depth_luma_minus1,
        pcm_sample_bit_depth_chroma_minus1,
        log2_min_pcm_luma_coding_block_size_minus3,
        log2_diff_max_min_pcm_luma_coding_block_size,
        pcm_loop_filter_disabled_flag,
        num_short_term_ref_pic_sets,
        shortTermRefPicSets,
        long_term_ref_pics_present_flag,
        num_long_term_ref_pics_sps,
        lt_ref_pic_poc_lsb_sps,
        used_by_curr_pic_lt_sps_flag,
        sps_temporal_mvp_enabled_flag,
        strong_intra_smoothing_enabled_flag,
        vui_parameters_present_flag,
        vuiParameters,
        sps_extension_present_flag,
        sps_range_extension_flag,
        sps_multilayer_extension_flag,
        sps_3d_extension_flag,
        sps_scc_extension_flag,
        sps_extension_4bits,
        spsMultilayerExtension,
        sps3dExtension,
        sps_extension_data_flag,
    };
}

/**
 * 7.3.3 Profile, tier and level syntax
 *
 * Common part between general_profile_tier_level and
 * sub_layer_profile_tier_level
 */
function h265ParseProfileTier(reader: NaluSodbBitReader) {
    const profile_space = reader.read(2);
    const tier_flag = !!reader.next();
    const profile_idc = reader.read(5);

    const profileCompatibilitySet = reader.peekBytes(4);
    const profile_compatibility_flag: boolean[] = [];
    for (let j = 0; j < 32; j += 1) {
        profile_compatibility_flag[j] = !!reader.next();
    }

    const constraintSet = reader.peekBytes(6);

    const progressive_source_flag = !!reader.next();
    const interlaced_source_flag = !!reader.next();
    const non_packed_constraint_flag = !!reader.next();
    const frame_only_constraint_flag = !!reader.next();

    let max_12bit_constraint_flag: boolean | undefined;
    let max_10bit_constraint_flag: boolean | undefined;
    let max_8bit_constraint_flag: boolean | undefined;
    let max_422chroma_constraint_flag: boolean | undefined;
    let max_420chroma_constraint_flag: boolean | undefined;
    let max_monochrome_constraint_flag: boolean | undefined;
    let intra_constraint_flag: boolean | undefined;
    let one_picture_only_constraint_flag: boolean | undefined;
    let lower_bit_rate_constraint_flag: boolean | undefined;
    let max_14bit_constraint_flag: boolean | undefined;
    if (
        profile_idc === 4 ||
        profile_compatibility_flag[4] ||
        profile_idc === 5 ||
        profile_compatibility_flag[5] ||
        profile_idc === 6 ||
        profile_compatibility_flag[6] ||
        profile_idc === 7 ||
        profile_compatibility_flag[7] ||
        profile_idc === 8 ||
        profile_compatibility_flag[8] ||
        profile_idc === 9 ||
        profile_compatibility_flag[9] ||
        profile_idc === 10 ||
        profile_compatibility_flag[10] ||
        profile_idc === 11 ||
        profile_compatibility_flag[11]
    ) {
        max_12bit_constraint_flag = !!reader.next();
        max_10bit_constraint_flag = !!reader.next();
        max_8bit_constraint_flag = !!reader.next();
        max_422chroma_constraint_flag = !!reader.next();
        max_420chroma_constraint_flag = !!reader.next();
        max_monochrome_constraint_flag = !!reader.next();
        intra_constraint_flag = !!reader.next();
        one_picture_only_constraint_flag = !!reader.next();
        lower_bit_rate_constraint_flag = !!reader.next();
        if (
            profile_idc === 5 ||
            profile_compatibility_flag[5] ||
            profile_idc === 9 ||
            profile_compatibility_flag[9] ||
            profile_idc === 10 ||
            profile_compatibility_flag[10] ||
            profile_idc === 11 ||
            profile_compatibility_flag[11]
        ) {
            max_14bit_constraint_flag = !!reader.next();
            reader.skip(33);
        } else {
            reader.skip(34);
        }
    } else if (profile_idc === 2 || profile_compatibility_flag[2]) {
        reader.skip(7);
        one_picture_only_constraint_flag = !!reader.next();
        reader.skip(35);
    } else {
        reader.skip(43);
    }

    let inbld_flag: boolean | undefined;
    if (
        profile_idc === 1 ||
        profile_compatibility_flag[1] ||
        profile_idc === 2 ||
        profile_compatibility_flag[2] ||
        profile_idc === 3 ||
        profile_compatibility_flag[3] ||
        profile_idc === 4 ||
        profile_compatibility_flag[4] ||
        profile_idc === 5 ||
        profile_compatibility_flag[5] ||
        profile_idc === 9 ||
        profile_compatibility_flag[9] ||
        profile_idc === 11 ||
        profile_compatibility_flag[11]
    ) {
        inbld_flag = !!reader.next();
    } else {
        reader.skip(1);
    }

    return {
        profile_space,
        tier_flag,
        profile_idc,
        profileCompatibilitySet,
        profile_compatibility_flag,
        constraintSet,
        progressive_source_flag,
        interlaced_source_flag,
        non_packed_constraint_flag,
        frame_only_constraint_flag,
        max_12bit_constraint_flag,
        max_10bit_constraint_flag,
        max_8bit_constraint_flag,
        max_422chroma_constraint_flag,
        max_420chroma_constraint_flag,
        max_monochrome_constraint_flag,
        intra_constraint_flag,
        one_picture_only_constraint_flag,
        lower_bit_rate_constraint_flag,
        max_14bit_constraint_flag,
        inbld_flag,
    };
}

export type H265ProfileTier = ReturnType<typeof h265ParseProfileTier>;

export interface H265ProfileTierLevel {
    generalProfileTier: H265ProfileTier | undefined;
    general_level_idc: number;
    sub_layer_profile_present_flag: boolean[];
    sub_layer_level_present_flag: boolean[];
    subLayerProfileTier: H265ProfileTier[];
    sub_layer_level_idc: number[];
}

/**
 * 7.3.3 Profile, tier and level syntax
 */
function h265ParseProfileTierLevel(
    reader: NaluSodbBitReader,
    profilePresentFlag: true,
    maxNumSubLayersMinus1: number,
): H265ProfileTierLevel & { generalProfileTier: H265ProfileTier };
function h265ParseProfileTierLevel(
    reader: NaluSodbBitReader,
    profilePresentFlag: false,
    maxNumSubLayersMinus1: number,
): H265ProfileTierLevel & { generalProfileTier: undefined };
function h265ParseProfileTierLevel(
    reader: NaluSodbBitReader,
    profilePresentFlag: boolean,
    maxNumSubLayersMinus1: number,
): H265ProfileTierLevel;
function h265ParseProfileTierLevel(
    reader: NaluSodbBitReader,
    profilePresentFlag: boolean,
    maxNumSubLayersMinus1: number,
): H265ProfileTierLevel {
    let generalProfileTier: H265ProfileTier | undefined;
    if (profilePresentFlag) {
        generalProfileTier = h265ParseProfileTier(reader);
    }

    const general_level_idc = reader.read(8);

    const sub_layer_profile_present_flag: boolean[] = [];
    const sub_layer_level_present_flag: boolean[] = [];
    for (let i = 0; i < maxNumSubLayersMinus1; i += 1) {
        sub_layer_profile_present_flag[i] = !!reader.next();
        sub_layer_level_present_flag[i] = !!reader.next();
    }

    if (maxNumSubLayersMinus1 > 0) {
        for (let i = maxNumSubLayersMinus1; i < 8; i += 1) {
            reader.read(2);
        }
    }

    const subLayerProfileTier: H265ProfileTier[] = [];
    const sub_layer_level_idc: number[] = [];
    for (let i = 0; i < maxNumSubLayersMinus1; i += 1) {
        if (sub_layer_profile_present_flag[i]) {
            subLayerProfileTier[i] = h265ParseProfileTier(reader);
        }
        if (sub_layer_level_present_flag[i]) {
            sub_layer_level_idc[i] = reader.read(8);
        }
    }

    return {
        generalProfileTier,
        general_level_idc,
        sub_layer_profile_present_flag,
        sub_layer_level_present_flag,
        subLayerProfileTier,
        sub_layer_level_idc,
    };
}

/**
 * 7.3.4 Scaling list data syntax
 */
export function h265ParseScalingListData(reader: NaluSodbBitReader) {
    const scaling_list: number[][][] = [];
    for (let sizeId = 0; sizeId < 4; sizeId += 1) {
        scaling_list[sizeId] = [];
        for (let matrixId = 0; matrixId < 6; matrixId += sizeId === 3 ? 3 : 1) {
            const scaling_list_pred_mode_flag = !!reader.next();
            if (!scaling_list_pred_mode_flag) {
                reader.decodeExponentialGolombNumber();
            } else {
                let nextCoef = 8;
                const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
                if (sizeId > 1) {
                    const scaling_list_dc_coef_minus8 =
                        reader.decodeExponentialGolombNumber();
                    nextCoef = scaling_list_dc_coef_minus8 + 8;
                }
                scaling_list[sizeId]![matrixId] = [];
                for (let i = 0; i < coefNum; i += 1) {
                    const scaling_list_delta_coef =
                        reader.decodeExponentialGolombNumber();
                    nextCoef = (nextCoef + scaling_list_delta_coef + 256) % 256;
                    scaling_list[sizeId]![matrixId]![i] = nextCoef;
                }
            }
        }
    }
    return scaling_list;
}

interface ShortTermReferencePictureSet {
    stRpsIdx: number;
    num_short_term_ref_pic_sets: number;

    inter_ref_pic_set_prediction_flag: boolean;
    delta_idx_minus1: number;
    delta_rps_sign: boolean;
    abs_delta_rps_minus1: number;
    used_by_curr_pic_flag: boolean[];
    use_delta_flag: boolean[];
    num_negative_pics: number;
    num_positive_pics: number;
    delta_poc_s0_minus1: number[];
    used_by_curr_pic_s0_flag: boolean[];
    delta_poc_s1_minus1: number[];
    used_by_curr_pic_s1_flag: boolean[];
}

/**
 * 7.3.7 Short-term reference picture set syntax
 */
export function h265ParseShortTermReferencePictureSet(
    reader: NaluSodbBitReader,
    stRpsIdx: number,
    num_short_term_ref_pic_sets: number,
    sets: ShortTermReferencePictureSet[],
): ShortTermReferencePictureSet {
    let inter_ref_pic_set_prediction_flag = false;
    if (stRpsIdx !== 0) {
        inter_ref_pic_set_prediction_flag = !!reader.next();
    }
    let delta_idx_minus1 = 0;
    let delta_rps_sign = false;
    let abs_delta_rps_minus1 = 0;
    const used_by_curr_pic_flag: boolean[] = [];
    const use_delta_flag: boolean[] = [];
    let num_negative_pics = 0;
    let num_positive_pics = 0;
    const delta_poc_s0_minus1: number[] = [];
    const used_by_curr_pic_s0_flag: boolean[] = [];
    const delta_poc_s1_minus1: number[] = [];
    const used_by_curr_pic_s1_flag: boolean[] = [];
    if (inter_ref_pic_set_prediction_flag) {
        if (stRpsIdx === num_short_term_ref_pic_sets) {
            delta_idx_minus1 = reader.decodeExponentialGolombNumber();
        }
        delta_rps_sign = !!reader.next();
        abs_delta_rps_minus1 = reader.decodeExponentialGolombNumber();
        const RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1);
        const RefRps = sets[RefRpsIdx]!;
        const NumDeltaPocs_RefRpsIdx =
            RefRps.num_negative_pics + RefRps.num_positive_pics;
        for (let j = 0; j <= NumDeltaPocs_RefRpsIdx; j += 1) {
            used_by_curr_pic_flag[j] = !!reader.next();
            if (!used_by_curr_pic_flag[j]!) {
                use_delta_flag[j] = !!reader.next();
            } else {
                use_delta_flag[j] = true;
            }
        }

        const DeltaRps =
            (1 - 2 * Number(delta_rps_sign)) * (abs_delta_rps_minus1 + 1);

        const RefPocS0: number[] = [];
        const RefPocS1: number[] = [];
        const pocS0: number[] = [];
        const pocS1: number[] = [];

        let dPoc = 0;
        for (let i = 0; i < RefRps.num_negative_pics; i += 1) {
            dPoc -= RefRps.delta_poc_s0_minus1[i]! + 1;
            RefPocS0[i] = dPoc;
        }
        dPoc = 0;
        for (let i = 0; i < RefRps.num_positive_pics; i += 1) {
            dPoc += RefRps.delta_poc_s1_minus1[i]! + 1;
            RefPocS1[i] = dPoc;
        }

        let i = 0;
        if (RefRps.num_positive_pics > 0) {
            for (let j = RefRps.num_positive_pics - 1; j >= 0; j -= 1) {
                dPoc = RefPocS1[j]! + DeltaRps;
                if (dPoc < 0 && use_delta_flag[RefRps.num_negative_pics + j]) {
                    pocS0[i] = dPoc;
                    used_by_curr_pic_s0_flag[i] =
                        used_by_curr_pic_flag[RefRps.num_negative_pics + j]!;
                    i += 1;
                }
            }
        }
        if (DeltaRps < 0 && use_delta_flag[NumDeltaPocs_RefRpsIdx]) {
            pocS0[i] = DeltaRps;
            used_by_curr_pic_s0_flag[i] =
                used_by_curr_pic_flag[NumDeltaPocs_RefRpsIdx]!;
            i += 1;
        }
        for (let j = 0; j < RefRps.num_negative_pics; j += 1) {
            dPoc = RefPocS0[j]! + DeltaRps;
            if (dPoc < 0 && use_delta_flag[j]) {
                pocS0[i] = dPoc;
                used_by_curr_pic_s0_flag[i] = used_by_curr_pic_flag[j]!;
                i += 1;
            }
        }

        num_negative_pics = i;
        let prev = 0;
        for (i = 0; i < num_negative_pics; i += 1) {
            const current = pocS0[i]!;
            delta_poc_s0_minus1[i] = -(current - prev - 1);
            prev = current;
        }

        i = 0;
        if (RefRps.num_negative_pics > 0) {
            for (let j = RefRps.num_negative_pics - 1; j >= 0; j -= 1) {
                dPoc = RefPocS0[j]! + DeltaRps;
                if (dPoc > 0 && use_delta_flag[j]) {
                    pocS1[i] = dPoc;
                    used_by_curr_pic_s1_flag[i] = used_by_curr_pic_flag[j]!;
                    i += 1;
                }
            }
        }
        if (DeltaRps > 0 && use_delta_flag[NumDeltaPocs_RefRpsIdx]) {
            pocS1[i] = DeltaRps;
            used_by_curr_pic_s1_flag[i] =
                used_by_curr_pic_flag[NumDeltaPocs_RefRpsIdx]!;
            i += 1;
        }
        for (let j = 0; j < RefRps.num_positive_pics; j += 1) {
            dPoc = RefPocS1[j]! + DeltaRps;
            if (dPoc > 0 && use_delta_flag[RefRps.num_negative_pics + j]) {
                pocS1[i] = dPoc;
                used_by_curr_pic_s1_flag[i] =
                    used_by_curr_pic_flag[RefRps.num_negative_pics + j]!;
                i += 1;
            }
        }

        num_positive_pics = i;
        prev = 0;
        for (i = 0; i < num_positive_pics; i += 1) {
            const current = pocS1[i]!;
            delta_poc_s1_minus1[i] = current - prev - 1;
            prev = current;
        }
    } else {
        num_negative_pics = reader.decodeExponentialGolombNumber();
        num_positive_pics = reader.decodeExponentialGolombNumber();
        for (let i = 0; i < num_negative_pics; i += 1) {
            delta_poc_s0_minus1[i] = reader.decodeExponentialGolombNumber();
            used_by_curr_pic_s0_flag[i] = !!reader.next();
        }
        for (let i = 0; i < num_positive_pics; i += 1) {
            delta_poc_s1_minus1[i] = reader.decodeExponentialGolombNumber();
            used_by_curr_pic_s1_flag[i] = !!reader.next();
        }
    }

    return {
        stRpsIdx,
        num_short_term_ref_pic_sets,
        inter_ref_pic_set_prediction_flag,
        delta_idx_minus1,
        delta_rps_sign,
        abs_delta_rps_minus1,
        used_by_curr_pic_flag,
        use_delta_flag,
        num_negative_pics,
        num_positive_pics,
        delta_poc_s0_minus1,
        used_by_curr_pic_s0_flag,
        delta_poc_s1_minus1,
        used_by_curr_pic_s1_flag,
    };
}

/**
 * E.2.1 VUI parameters syntax
 */
export function h265ParseVuiParameters(
    reader: NaluSodbBitReader,
    sps_max_sub_layers_minus1: number,
) {
    const aspect_ratio_info_present_flag = !!reader.next();
    let aspect_ratio_idc: number | undefined;
    let sar_width: number | undefined;
    let sar_height: number | undefined;
    if (aspect_ratio_info_present_flag) {
        aspect_ratio_idc = reader.read(8);
        if (aspect_ratio_idc === 255) {
            sar_width = reader.read(16);
            sar_height = reader.read(16);
        }
    }

    const overscan_info_present_flag = !!reader.next();
    let overscan_appropriate_flag: boolean | undefined;
    if (overscan_info_present_flag) {
        overscan_appropriate_flag = !!reader.next();
    }

    const video_signal_type_present_flag = !!reader.next();
    let video_format: number | undefined;
    let video_full_range_flag: boolean | undefined;
    let colour_description_present_flag: boolean | undefined;
    let colour_primaries: number | undefined;
    let transfer_characteristics: number | undefined;
    let matrix_coeffs: number | undefined;
    if (video_signal_type_present_flag) {
        video_format = reader.read(3);
        video_full_range_flag = !!reader.next();
        colour_description_present_flag = !!reader.next();
        if (colour_description_present_flag) {
            colour_primaries = reader.read(8);
            transfer_characteristics = reader.read(8);
            matrix_coeffs = reader.read(8);
        }
    }

    const chroma_loc_info_present_flag = !!reader.next();
    let chroma_sample_loc_type_top_field: number | undefined;
    let chroma_sample_loc_type_bottom_field: number | undefined;
    if (chroma_loc_info_present_flag) {
        chroma_sample_loc_type_top_field =
            reader.decodeExponentialGolombNumber();
        chroma_sample_loc_type_bottom_field =
            reader.decodeExponentialGolombNumber();
    }

    const neutral_chroma_indication_flag = !!reader.next();
    const field_seq_flag = !!reader.next();
    const frame_field_info_present_flag = !!reader.next();

    const default_display_window_flag = !!reader.next();
    let def_disp_win_left_offset: number | undefined;
    let def_disp_win_right_offset: number | undefined;
    let def_disp_win_top_offset: number | undefined;
    let def_disp_win_bottom_offset: number | undefined;
    if (default_display_window_flag) {
        def_disp_win_left_offset = reader.decodeExponentialGolombNumber();
        def_disp_win_right_offset = reader.decodeExponentialGolombNumber();
        def_disp_win_top_offset = reader.decodeExponentialGolombNumber();
        def_disp_win_bottom_offset = reader.decodeExponentialGolombNumber();
    }

    const vui_timing_info_present_flag = !!reader.next();
    let vui_num_units_in_tick: number | undefined;
    let vui_time_scale: number | undefined;
    let vui_poc_proportional_to_timing_flag: boolean | undefined;
    let vui_num_ticks_poc_diff_one_minus1: number | undefined;
    let vui_hrd_parameters_present_flag: boolean | undefined;
    let vui_hrd_parameters: H265HrdParameters | undefined;
    if (vui_timing_info_present_flag) {
        vui_num_units_in_tick = reader.read(32);
        vui_time_scale = reader.read(32);
        vui_poc_proportional_to_timing_flag = !!reader.next();
        if (vui_poc_proportional_to_timing_flag) {
            vui_num_ticks_poc_diff_one_minus1 =
                reader.decodeExponentialGolombNumber();
        }
        vui_hrd_parameters_present_flag = !!reader.next();
        if (vui_hrd_parameters_present_flag) {
            vui_hrd_parameters = h265ParseHrdParameters(
                reader,
                true,
                sps_max_sub_layers_minus1,
            );
        }
    }

    const bitstream_restriction_flag = !!reader.next();
    let tiles_fixed_structure_flag: boolean | undefined;
    let motion_vectors_over_pic_boundaries_flag: boolean | undefined;
    let restricted_ref_pic_lists_flag: boolean | undefined;
    let min_spatial_segmentation_idc: number | undefined;
    let max_bytes_per_pic_denom: number | undefined;
    let max_bits_per_min_cu_denom: number | undefined;
    let log2_max_mv_length_horizontal: number | undefined;
    let log2_max_mv_length_vertical: number | undefined;
    if (bitstream_restriction_flag) {
        tiles_fixed_structure_flag = !!reader.next();
        motion_vectors_over_pic_boundaries_flag = !!reader.next();
        restricted_ref_pic_lists_flag = !!reader.next();
        min_spatial_segmentation_idc = reader.decodeExponentialGolombNumber();
        max_bytes_per_pic_denom = reader.decodeExponentialGolombNumber();
        max_bits_per_min_cu_denom = reader.decodeExponentialGolombNumber();
        log2_max_mv_length_horizontal = reader.decodeExponentialGolombNumber();
        log2_max_mv_length_vertical = reader.decodeExponentialGolombNumber();
    }

    return {
        aspect_ratio_info_present_flag,
        aspect_ratio_idc,
        sar_width,
        sar_height,

        overscan_info_present_flag,
        overscan_appropriate_flag,

        video_signal_type_present_flag,
        video_format,
        video_full_range_flag,
        colour_description_present_flag,
        colour_primaries,
        transfer_characteristics,
        matrix_coeffs,

        chroma_loc_info_present_flag,
        chroma_sample_loc_type_top_field,
        chroma_sample_loc_type_bottom_field,

        neutral_chroma_indication_flag,
        field_seq_flag,
        frame_field_info_present_flag,

        default_display_window_flag,
        def_disp_win_left_offset,
        def_disp_win_right_offset,
        def_disp_win_top_offset,
        def_disp_win_bottom_offset,

        vui_timing_info_present_flag,
        vui_num_units_in_tick,
        vui_time_scale,
        vui_poc_proportional_to_timing_flag,
        vui_num_ticks_poc_diff_one_minus1,
        vui_hrd_parameters_present_flag,
        vui_hrd_parameters,

        bitstream_restriction_flag,
        tiles_fixed_structure_flag,
        motion_vectors_over_pic_boundaries_flag,
        restricted_ref_pic_lists_flag,
        min_spatial_segmentation_idc,
        max_bytes_per_pic_denom,
        max_bits_per_min_cu_denom,
        log2_max_mv_length_horizontal,
        log2_max_mv_length_vertical,
    };
}

export type H265VuiParameters = ReturnType<typeof h265ParseVuiParameters>;

/**
 * E.2.2 HRD parameters syntax
 */
export function h265ParseHrdParameters(
    reader: NaluSodbBitReader,
    commonInfPresentFlag: boolean,
    maxNumSubLayersMinus1: number,
) {
    let nal_hrd_parameters_present_flag: boolean | undefined;
    let vcl_hrd_parameters_present_flag: boolean | undefined;
    let sub_pic_hrd_params_present_flag: boolean | undefined;
    let tick_divisor_minus2: number | undefined;
    let du_cpb_removal_delay_increment_length_minus1: number | undefined;
    let sub_pic_cpb_params_in_pic_timing_sei_flag: boolean | undefined;
    let dpb_output_delay_du_length_minus1: number | undefined;
    let bit_rate_scale: number | undefined;
    let cpb_size_scale: number | undefined;
    let cpb_size_du_scale: number | undefined;
    let initial_cpb_removal_delay_length_minus1: number | undefined;
    let au_cpb_removal_delay_length_minus1: number | undefined;
    let dpb_output_delay_length_minus1: number | undefined;
    if (commonInfPresentFlag) {
        nal_hrd_parameters_present_flag = !!reader.next();
        vcl_hrd_parameters_present_flag = !!reader.next();
        if (
            nal_hrd_parameters_present_flag ||
            vcl_hrd_parameters_present_flag
        ) {
            sub_pic_hrd_params_present_flag = !!reader.next();
            if (sub_pic_hrd_params_present_flag) {
                tick_divisor_minus2 = reader.read(8);
                du_cpb_removal_delay_increment_length_minus1 = reader.read(5);
                sub_pic_cpb_params_in_pic_timing_sei_flag = !!reader.next();
                dpb_output_delay_du_length_minus1 = reader.read(5);
            }
            bit_rate_scale = reader.read(4);
            cpb_size_scale = reader.read(4);
            if (sub_pic_hrd_params_present_flag) {
                cpb_size_du_scale = reader.read(4);
            }
            initial_cpb_removal_delay_length_minus1 = reader.read(5);
            au_cpb_removal_delay_length_minus1 = reader.read(5);
            dpb_output_delay_length_minus1 = reader.read(5);
        }
    }

    const fixed_pic_rate_general_flag: boolean[] = [];
    const fixed_pic_rate_within_cvs_flag: boolean[] = [];
    const elemental_duration_in_tc_minus1: number[] = [];
    const low_delay_hrd_flag: boolean[] = [];
    const cpb_cnt_minus1: number[] = [];
    const nalHrdParameters: SubLayerHrdParameters[] = [];
    const vclHrdParameters: SubLayerHrdParameters[] = [];
    for (let i = 0; i <= maxNumSubLayersMinus1; i += 1) {
        fixed_pic_rate_general_flag[i] = !!reader.next();
        if (!fixed_pic_rate_general_flag[i]) {
            fixed_pic_rate_within_cvs_flag[i] = !!reader.next();
        }
        if (fixed_pic_rate_within_cvs_flag[i]) {
            elemental_duration_in_tc_minus1[i] =
                reader.decodeExponentialGolombNumber();
        } else {
            low_delay_hrd_flag[i] = !!reader.next();
        }

        if (!low_delay_hrd_flag[i]) {
            cpb_cnt_minus1[i] = reader.decodeExponentialGolombNumber();
        }

        if (nal_hrd_parameters_present_flag) {
            nalHrdParameters[i] = h265ParseSubLayerHrdParameters(
                reader,
                i,
                getCpbCnt(cpb_cnt_minus1[i]!),
            );
        }
        if (vcl_hrd_parameters_present_flag) {
            vclHrdParameters[i] = h265ParseSubLayerHrdParameters(
                reader,
                i,
                getCpbCnt(cpb_cnt_minus1[i]!),
            );
        }
    }

    return {
        nal_hrd_parameters_present_flag,
        vcl_hrd_parameters_present_flag,
        sub_pic_hrd_params_present_flag,
        tick_divisor_minus2,
        du_cpb_removal_delay_increment_length_minus1,
        sub_pic_cpb_params_in_pic_timing_sei_flag,
        dpb_output_delay_du_length_minus1,
        bit_rate_scale,
        cpb_size_scale,
        cpb_size_du_scale,
        initial_cpb_removal_delay_length_minus1,
        au_cpb_removal_delay_length_minus1,
        dpb_output_delay_length_minus1,
        fixed_pic_rate_general_flag,
        fixed_pic_rate_within_cvs_flag,
        elemental_duration_in_tc_minus1,
        low_delay_hrd_flag,
        cpb_cnt_minus1,
        nalHrdParameters,
        vclHrdParameters,
    };
}

export type H265HrdParameters = ReturnType<typeof h265ParseHrdParameters>;

/**
 * E.2.3 Sub-layer HRD parameters syntax
 */
export function h265ParseSubLayerHrdParameters(
    reader: NaluSodbBitReader,
    subLayerId: number,
    CpbCnt: number,
) {
    const bit_rate_value_minus1: number[] = [];
    const cpb_size_value_minus1: number[] = [];
    const cpb_size_du_value_minus1: number[] = [];
    const bit_rate_du_value_minus1: number[] = [];
    const cbr_flag: boolean[] = [];
    for (let i = 0; i < CpbCnt; i += 1) {
        bit_rate_value_minus1[i] = reader.decodeExponentialGolombNumber();
        cpb_size_value_minus1[i] = reader.decodeExponentialGolombNumber();
        if (subLayerId > 0) {
            cbr_flag[i] = !!reader.next();
        }
    }
    return {
        bit_rate_value_minus1,
        cpb_size_value_minus1,
        cpb_size_du_value_minus1,
        bit_rate_du_value_minus1,
        cbr_flag,
    };
}

/**
 * E.3.3 Sub-layer HRD parameters semantics
 */
function getCpbCnt(cpb_cnt_minus_1: number) {
    return cpb_cnt_minus_1 + 1;
}

export function h265SearchConfiguration(buffer: Uint8Array) {
    let videoParameterSet!: H265NaluRaw;
    let sequenceParameterSet!: H265NaluRaw;
    let pictureParameterSet!: H265NaluRaw;
    let count = 0;

    for (const nalu of annexBSplitNalu(buffer)) {
        const header = h265ParseNaluHeader(nalu);
        const raw: H265NaluRaw = {
            ...header,
            data: nalu,
            rbsp: nalu.subarray(2),
        };
        switch (header.nal_unit_type) {
            case 32:
                videoParameterSet = raw;
                break;
            case 33:
                sequenceParameterSet = raw;
                break;
            case 34:
                pictureParameterSet = raw;
                break;
            default:
                continue;
        }

        count += 1;
        if (count === 3) {
            return {
                videoParameterSet,
                sequenceParameterSet,
                pictureParameterSet,
            };
        }
    }

    throw new Error("Invalid data");
}

export function h265ParseSpsMultilayerExtension(reader: NaluSodbBitReader) {
    const inter_view_mv_vert_constraint_flag = !!reader.next();
    return {
        inter_view_mv_vert_constraint_flag,
    };
}

export type H265SpsMultilayerExtension = ReturnType<
    typeof h265ParseSpsMultilayerExtension
>;

export function h265ParseSps3dExtension(reader: NaluSodbBitReader) {
    const iv_di_mc_enabled_flag: boolean[] = [];
    const iv_mv_scal_enabled_flag: boolean[] = [];

    iv_di_mc_enabled_flag[0] = !!reader.next();
    iv_mv_scal_enabled_flag[0] = !!reader.next();

    const log2_ivmc_sub_pb_size_minus3 = reader.decodeExponentialGolombNumber();
    const iv_res_pred_enabled_flag = !!reader.next();
    const depth_ref_enabled_flag = !!reader.next();
    const vsp_mc_enabled_flag = !!reader.next();
    const dbbp_enabled_flag = !!reader.next();

    iv_di_mc_enabled_flag[1] = !!reader.next();
    iv_mv_scal_enabled_flag[1] = !!reader.next();

    const tex_mc_enabled_flag = !!reader.next();
    const log2_texmc_sub_pb_size_minus3 =
        reader.decodeExponentialGolombNumber();
    const intra_contour_enabled_flag = !!reader.next();
    const intra_dc_only_wedge_enabled_flag = !!reader.next();
    const cqt_cu_part_pred_enabled_flag = !!reader.next();
    const inter_dc_only_enabled_flag = !!reader.next();
    const skip_intra_enabled_flag = !!reader.next();

    return {
        iv_di_mc_enabled_flag,
        iv_mv_scal_enabled_flag,
        log2_ivmc_sub_pb_size_minus3,
        iv_res_pred_enabled_flag,
        depth_ref_enabled_flag,
        vsp_mc_enabled_flag,
        dbbp_enabled_flag,
        tex_mc_enabled_flag,
        log2_texmc_sub_pb_size_minus3,
        intra_contour_enabled_flag,
        intra_dc_only_wedge_enabled_flag,
        cqt_cu_part_pred_enabled_flag,
        inter_dc_only_enabled_flag,
        skip_intra_enabled_flag,
    };
}

export type H265Sps3dExtension = ReturnType<typeof h265ParseSps3dExtension>;

export interface H265Configuration {
    videoParameterSet: H265NaluRaw;
    sequenceParameterSet: H265NaluRaw;
    pictureParameterSet: H265NaluRaw;

    generalProfileSpace: number;
    generalProfileIndex: number;
    generalProfileCompatibilitySet: Uint8Array;
    generalTierFlag: boolean;
    generalLevelIndex: number;
    generalConstraintSet: Uint8Array;

    encodedWidth: number;
    encodedHeight: number;

    cropLeft: number;
    cropRight: number;
    cropTop: number;
    cropBottom: number;
    croppedWidth: number;
    croppedHeight: number;
}

export function h265ParseConfiguration(data: Uint8Array): H265Configuration {
    const { videoParameterSet, sequenceParameterSet, pictureParameterSet } =
        h265SearchConfiguration(data);

    const {
        profileTierLevel: {
            generalProfileTier: {
                profile_space: generalProfileSpace,
                tier_flag: generalTierFlag,
                profile_idc: generalProfileIndex,
                profileCompatibilitySet: generalProfileCompatibilitySet,
                constraintSet: generalConstraintSet,
            },
            general_level_idc: generalLevelIndex,
        },
    } = h265ParseVideoParameterSet(videoParameterSet.rbsp);

    const {
        chroma_format_idc,
        pic_width_in_luma_samples: encodedWidth,
        pic_height_in_luma_samples: encodedHeight,
        conf_win_left_offset: cropLeft = 0,
        conf_win_right_offset: cropRight = 0,
        conf_win_top_offset: cropTop = 0,
        conf_win_bottom_offset: cropBottom = 0,
    } = h265ParseSequenceParameterSet(sequenceParameterSet.rbsp);

    const SubWidthC = getSubWidthC(chroma_format_idc);
    const SubHeightC = getSubHeightC(chroma_format_idc);

    const croppedWidth = encodedWidth - SubWidthC * (cropLeft + cropRight);
    const croppedHeight = encodedHeight - SubHeightC * (cropTop + cropBottom);

    return {
        videoParameterSet,
        sequenceParameterSet,
        pictureParameterSet,

        generalProfileSpace,
        generalProfileIndex,
        generalProfileCompatibilitySet,
        generalTierFlag,
        generalLevelIndex,
        generalConstraintSet,

        encodedWidth,
        encodedHeight,
        cropLeft,
        cropRight,
        cropTop,
        cropBottom,
        croppedWidth,
        croppedHeight,
    };
}
