"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Music = void 0;
const Client_1 = require("../api/resources/music/client/Client");
const core = __importStar(require("../core"));
class Music {
    constructor(options = {}) {
        this._options = options;
        this._client = new Client_1.MusicClient(options);
    }
    /**
     * Get the composition plan client
     */
    get compositionPlan() {
        return this._client.compositionPlan;
    }
    /**
     * Compose a song from a prompt or a composition plan.
     * @throws {@link ElevenLabs.UnprocessableEntityError}
     */
    compose(request = {}, requestOptions) {
        return this._client.compose(request, requestOptions);
    }
    // Private method for structural compatibility with generated Music class
    __compose(request = {}, requestOptions) {
        // This method exists for type compatibility only
        // The actual implementation is delegated through compose()
        throw new Error("Internal method - should not be called directly");
    }
    /**
     * Compose a song from a prompt or a composition plan with detailed response parsing.
     *
     * Unlike the standard `compose()` method which returns a raw audio stream, this method
     * automatically parses the multipart response to extract both the audio file and rich
     * metadata about the generated composition.
     *
     * @param request - The music composition request containing either:
     *   - `prompt`: A text description of the desired music (e.g., "upbeat electronic dance music")
     *   - `compositionPlan`: A detailed composition plan object created via `compositionPlan.create()`
     *   - `musicLengthMs`: Optional duration in milliseconds (10000-300000ms)
     *
     * @param requestOptions - Optional request configuration (e.g., timeout, signal, headers)
     *
     * @returns A promise that resolves to a `MultipartResponse` containing:
     *   - `json.compositionPlan`: The detailed composition plan structure including sections,
     *     styles, and lyrics
     *   - `json.songMetadata`: Metadata about the generated song (title, description, genres,
     *     languages, explicit content flag)
     *   - `audio`: Buffer containing the complete audio file (MP3 format)
     *   - `filename`: Suggested filename for the audio file
     *
     * @throws {@link ElevenLabs.UnprocessableEntityError} If the request parameters are invalid
     *   or the music generation fails
     *
     * @example
     * ```typescript
     * const client = new ElevenLabs({ apiKey: "your-api-key" });
     *
     * // Generate music from a text prompt
     * const response = await client.music.composeDetailed({
     *   prompt: "Epic orchestral music with dramatic strings and powerful brass",
     *   musicLengthMs: 60000 // 60 seconds
     * });
     *
     * console.log("Title:", response.json.songMetadata.title);
     * console.log("Genres:", response.json.songMetadata.genres);
     *
     * // Save the audio to a file
     * fs.writeFileSync(response.filename, response.audio);
     * ```
     *
     * @example
     * ```typescript
     * // Generate music from a composition plan
     * const plan = await client.music.compositionPlan.create({
     *   prompt: "A progressive rock song about space exploration"
     * });
     *
     * const response = await client.music.composeDetailed({
     *   compositionPlan: plan.compositionPlan
     * });
     *
     * // Access detailed section information
     * for (const section of response.json.compositionPlan.sections) {
     *   console.log(`${section.sectionName}: ${section.durationMs}ms`);
     * }
     * ```
     */
    composeDetailed(request = {}, requestOptions) {
        return core.HttpResponsePromise.fromPromise(this._composeDetailed(request, requestOptions));
    }
    _composeDetailed() {
        return __awaiter(this, arguments, void 0, function* (request = {}, requestOptions) {
            var _a;
            // Call the base client method to get the stream with raw response
            try {
                const { data: stream, rawResponse } = yield this._client
                    .composeDetailed(request, requestOptions)
                    .withRawResponse();
                // Parse the stream using the existing parsing method
                const parsedResponse = yield this.parseMultipart(stream);
                // Extract song-id from response headers (available when store_for_inpainting is true)
                const songId = (_a = rawResponse.headers.get("song-id")) !== null && _a !== void 0 ? _a : undefined;
                return { data: Object.assign(Object.assign({}, parsedResponse), { songId }), rawResponse };
            }
            catch (error) {
                const message = error instanceof Error ? error.message : String(error);
                throw new Error(`Failed to parse detailed composition response: ${message}`);
            }
        });
    }
    // Private method for structural compatibility with generated Music class
    __composeDetailed(request = {}, requestOptions) {
        // This method exists for type compatibility only
        // The actual implementation is delegated through composeDetailed()
        throw new Error("Internal method - should not be called directly");
    }
    /**
     * Stream a composed song from a prompt or a composition plan.
     * @throws {@link ElevenLabs.UnprocessableEntityError}
     */
    stream(request = {}, requestOptions) {
        return this._client.stream(request, requestOptions);
    }
    // Private method for structural compatibility with generated Music class
    __stream(request = {}, requestOptions) {
        // This method exists for type compatibility only
        // The actual implementation is delegated through stream()
        throw new Error("Internal method - should not be called directly");
    }
    /**
     * Separate a music file into individual stems
     * @throws {@link ElevenLabs.UnprocessableEntityError}
     */
    separateStems(request, requestOptions) {
        return this._client.separateStems(request, requestOptions);
    }
    // Private method for structural compatibility with generated Music class
    __separateStems(request, requestOptions) {
        // This method exists for type compatibility only
        // The actual implementation is delegated through separateStems()
        throw new Error("Internal method - should not be called directly");
    }
    /**
     * Reads a ReadableStream containing multipart data and parses it into JSON and audio parts
     * @param stream - ReadableStream from ElevenLabs music API response
     * @returns Object containing parsed JSON metadata, audio Buffer, and filename
     */
    parseMultipart(stream) {
        return __awaiter(this, void 0, void 0, function* () {
            // Read the stream and collect all chunks
            const reader = stream.getReader();
            const chunks = [];
            let done = false;
            while (!done) {
                const result = yield reader.read();
                done = result.done;
                if (result.value) {
                    chunks.push(result.value);
                }
            }
            // Combine all chunks into a single buffer
            const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
            const responseBytes = new Uint8Array(totalLength);
            let offset = 0;
            for (const chunk of chunks) {
                responseBytes.set(chunk, offset);
                offset += chunk.length;
            }
            // Parse the multipart content
            const responseText = new TextDecoder().decode(responseBytes);
            const lines = responseText.split("\n");
            const boundary = lines[0].trim();
            // Find the JSON part (should be early in the response)
            let jsonData = null;
            let filename = "generated_music.mp3";
            // Parse JSON from the text representation
            for (let i = 0; i < Math.min(10, lines.length); i++) {
                if (lines[i].includes("Content-Type: application/json") && i + 2 < lines.length) {
                    const jsonLine = lines[i + 2];
                    if (jsonLine.trim() && jsonLine.startsWith("{")) {
                        try {
                            const rawJson = JSON.parse(jsonLine);
                            // Convert snake_case keys to camelCase
                            jsonData = this.toCamelCase(rawJson);
                            console.log("✓ Successfully parsed JSON metadata");
                        }
                        catch (e) {
                            console.warn("Failed to parse JSON:", e);
                        }
                        break;
                    }
                }
            }
            // Extract filename from headers
            for (let i = 0; i < Math.min(20, lines.length); i++) {
                if (lines[i].includes("filename=")) {
                    const match = lines[i].match(/filename="([^"]+)"/);
                    if (match) {
                        filename = match[1];
                        break;
                    }
                }
            }
            // Find where the audio data starts (after the second boundary and headers)
            const boundaryBytes = new TextEncoder().encode(boundary);
            let firstBoundary = -1;
            let secondBoundary = -1;
            for (let i = 0; i <= responseBytes.length - boundaryBytes.length; i++) {
                let match = true;
                for (let j = 0; j < boundaryBytes.length; j++) {
                    if (responseBytes[i + j] !== boundaryBytes[j]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    if (firstBoundary === -1) {
                        firstBoundary = i;
                    }
                    else if (secondBoundary === -1) {
                        secondBoundary = i;
                        break;
                    }
                }
            }
            if (secondBoundary === -1) {
                throw new Error("Could not find audio part boundary");
            }
            // Find the start of audio data (after headers and empty line)
            let audioStart = secondBoundary + boundaryBytes.length;
            audioStart = this.findAudioStartIndex(responseBytes, audioStart);
            // Find the closing boundary to properly terminate the audio data
            // Multipart responses end with: \r\n--boundary--\r\n or \n--boundary--\n
            // Try \r\n first (HTTP standard), then fall back to \n
            const closingBoundaryCRLF = new TextEncoder().encode("\r\n" + boundary + "--");
            const closingBoundaryLF = new TextEncoder().encode("\n" + boundary + "--");
            let audioEnd = responseBytes.length;
            // Try CRLF first, then LF
            let foundAt = this.findIndex(responseBytes, audioStart, closingBoundaryCRLF);
            if (foundAt === -1) {
                foundAt = this.findIndex(responseBytes, audioStart, closingBoundaryLF);
            }
            if (foundAt !== -1) {
                audioEnd = foundAt;
            }
            const audioBuffer = Buffer.from(responseBytes.slice(audioStart, audioEnd));
            if (!jsonData) {
                throw new Error("Could not parse JSON data");
            }
            return {
                json: jsonData,
                audio: audioBuffer,
                filename: filename,
            };
        });
    }
    /**
     * Converts snake_case keys to camelCase recursively
     */
    toCamelCase(obj) {
        if (Array.isArray(obj)) {
            return obj.map((item) => this.toCamelCase(item));
        }
        else if (obj !== null && typeof obj === "object") {
            return Object.keys(obj).reduce((result, key) => {
                const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
                result[camelKey] = this.toCamelCase(obj[key]);
                return result;
            }, {});
        }
        return obj;
    }
    findIndex(responseBytes, audioStart, pattern) {
        for (let i = audioStart; i <= responseBytes.length - pattern.length; i++) {
            let match = true;
            for (let j = 0; j < pattern.length; j++) {
                if (responseBytes[i + j] !== pattern[j]) {
                    match = false;
                    break;
                }
            }
            if (match) {
                return i;
            }
        }
        return -1;
    }
    findAudioStartIndex(responseBytes, startIndex) {
        const foundAtCRLF = this.findIndex(responseBytes, startIndex, new TextEncoder().encode("\r\n\r\n"));
        if (foundAtCRLF !== -1) {
            return foundAtCRLF + 4;
        }
        const foundAtLF = this.findIndex(responseBytes, startIndex, new TextEncoder().encode("\n\n"));
        if (foundAtLF !== -1) {
            return foundAtLF + 2;
        }
        throw new Error("Could not find body start index");
    }
}
exports.Music = Music;
