///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2026, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
//   This application incorporates Open Design Alliance software pursuant to a
//   license agreement with Open Design Alliance.
//   Open Design Alliance Copyright (C) 2002-2026 by Open Design Alliance.
//   All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////

class FetchError extends Error {
  public status: number;

  constructor(status: number, message: string) {
    super(message);
    this.name = "FetchError";
    this.status = status;
  }
}

export interface Range {
  offset: number;
  length: number;
}

export class RangesLoader {
  private requestHeader: HeadersInit;
  private withCredentials: boolean;
  private abortSignal: AbortSignal | undefined;

  constructor() {
    this.requestHeader = {};
    this.withCredentials = false;
    this.abortSignal = undefined;
  }

  setRequestHeader(requestHeader: HeadersInit) {
    this.requestHeader = requestHeader;
  }

  setWithCredentials(withCredentials: boolean) {
    this.withCredentials = withCredentials;
  }

  setAbortSignal(abortSignal: AbortSignal) {
    this.abortSignal = abortSignal;
  }

  async load(url: string, ranges: Range[]): Promise<ArrayBuffer> {
    const init: RequestInit = {
      headers: {
        ...this.requestHeader,
        Range: "bytes=" + ranges.map((x) => `${x.offset}-${x.offset + x.length - 1}`).join(","),
      },
      credentials: this.withCredentials ? "include" : "same-origin",
      signal: this.abortSignal,
    };

    const response = await fetch(url, init);
    if (!response.ok) {
      throw new FetchError(response.status, `Failed to fetch "${url}", status ${response.status}`);
    }

    if (response.status !== 206) {
      const arrayBuffer = await response.arrayBuffer();
      return this.extractRanges(arrayBuffer, ranges);
    }

    return response.arrayBuffer();
  }

  // ===================== AI-CODE-START ======================
  // Source: Claude Sonnet 4.5
  // Date: 2025-28-10
  // Reviewer: roman.mochalov@opendesign.com
  // Issue: CLOUD-5933

  extractRanges(arrayBuffer: ArrayBuffer, ranges: Range[]): ArrayBuffer {
    const totalLength = ranges.reduce((sum, range) => sum + range.length, 0);
    const result = new Uint8Array(totalLength);

    let offset = 0;
    for (const range of ranges) {
      const chunk = new Uint8Array(arrayBuffer, range.offset, range.length);
      result.set(chunk, offset);
      offset += range.length;
    }

    return result.buffer;
  }
  // ===================== AI-CODE-END ======================
}
