import type {
  IDataObject,
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeDescription,
  IRequestOptions,
} from "n8n-workflow";
import { NodeOperationError } from "n8n-workflow";

const LAYOUT_TEMPLATES = [
  "COVER_LEFT_TEXT_RIGHT",
  "COVER_TOP_TEXT_BOTTOM",
  "COVER_RIGHT_TEXT_LEFT",
  "CENTERED_COMPACT",
  "LOWER_LEFT_COVER_TEXT",
  "LOWER_RIGHT_COVER_TEXT",
] as const;

const RESOLUTIONS = [
  "1920x1080",
  "1280x720",
  "854x480",
  "720x720",
  "640x360",
  "426x240",
] as const;

const PRIVACY = ["PUBLIC", "PRIVATE", "UNLISTED"] as const;

const WATERMARK_MODES = ["none", "default", "text", "logo"] as const;

const WATERMARK_POSITIONS = [
  "top-left",
  "top-right",
  "bottom-left",
  "bottom-right",
  "center",
] as const;

const FONT_KEYS = [
  "system",
  "inter",
  "montserrat",
  "roboto",
  "oswald",
  "playfair",
  "custom",
] as const;

/**
 * Full Songs2VID REST surface for n8n — every documented /api/v1 endpoint.
 * Optional fields live under Additional Fields collections.
 */
export class Songs2Vid implements INodeType {
  description: INodeTypeDescription = {
    displayName: "Songs2VID",
    name: "songs2Vid",
    icon: "file:songs2vid.svg",
    group: ["transform"],
    version: 1,
    subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
    description:
      "Upload media, create YouTube renders, poll jobs, manage playlists & API keys",
    defaults: {
      name: "Songs2VID",
    },
    inputs: ["main"],
    outputs: ["main"],
    credentials: [
      {
        name: "songs2VidApi",
        required: true,
        displayOptions: {
          show: {
            resource: [
              "file",
              "render",
              "playlist",
              "apiKey",
            ],
          },
        },
      },
      {
        name: "songs2VidApi",
        required: false,
        displayOptions: {
          show: {
            resource: ["discovery"],
          },
        },
      },
    ],
    requestDefaults: {
      baseURL: '={{($credentials.baseUrl || "https://songs2vid.com").replace(/\\/$/, "")}}',
      headers: {
        Accept: "application/json",
      },
    },
    properties: [
      {
        displayName: "Resource",
        name: "resource",
        type: "options",
        noDataExpression: true,
        options: [
          { name: "API Key", value: "apiKey" },
          { name: "Discovery", value: "discovery" },
          { name: "File", value: "file" },
          { name: "Playlist", value: "playlist" },
          { name: "Render", value: "render" },
        ],
        default: "render",
      },

      // ── Discovery ──────────────────────────────────────────────
      {
        displayName: "Operation",
        name: "operation",
        type: "options",
        noDataExpression: true,
        displayOptions: { show: { resource: ["discovery"] } },
        options: [
          {
            name: "Get API Catalog",
            value: "getCatalog",
            description: "GET /api/v1 — endpoints, limits, layout templates (no auth)",
            action: "Get API catalog",
          },
        ],
        default: "getCatalog",
      },

      // ── File ───────────────────────────────────────────────────
      {
        displayName: "Operation",
        name: "operation",
        type: "options",
        noDataExpression: true,
        displayOptions: { show: { resource: ["file"] } },
        options: [
          {
            name: "Upload",
            value: "upload",
            description: "POST /api/v1/upload — image | audio | logo | font",
            action: "Upload a file",
          },
        ],
        default: "upload",
      },
      {
        displayName: "Binary Property",
        name: "binaryPropertyName",
        type: "string",
        default: "data",
        required: true,
        displayOptions: {
          show: { resource: ["file"], operation: ["upload"] },
        },
        description: "Name of the binary property that holds the file",
      },
      {
        displayName: "Type",
        name: "uploadType",
        type: "options",
        required: true,
        options: [
          { name: "Image (cover)", value: "image" },
          { name: "Audio", value: "audio" },
          { name: "Logo (PNG watermark)", value: "logo" },
          { name: "Font (.ttf / .otf)", value: "font" },
        ],
        default: "audio",
        displayOptions: {
          show: { resource: ["file"], operation: ["upload"] },
        },
      },

      // ── Render ─────────────────────────────────────────────────
      {
        displayName: "Operation",
        name: "operation",
        type: "options",
        noDataExpression: true,
        displayOptions: { show: { resource: ["render"] } },
        options: [
          {
            name: "Create Render",
            value: "create",
            description: "POST /api/v1/render — start encode + YouTube upload",
            action: "Create a render",
          },
          {
            name: "Create Job (alias)",
            value: "createJob",
            description: "POST /api/v1/jobs — identical to Create Render",
            action: "Create a job",
          },
          {
            name: "Create Batch",
            value: "createBatch",
            description:
              "POST /api/v1/jobs/batch — one-shot multipart (no webhookUrl)",
            action: "Create a batch job",
          },
          {
            name: "Get Render Status",
            value: "get",
            description: "GET /api/v1/jobs/:id",
            action: "Get render status",
          },
          {
            name: "List Renders",
            value: "list",
            description: "GET /api/v1/jobs (or /api/v1/render)",
            action: "List renders",
          },
        ],
        default: "create",
      },
      {
        displayName: "Use Render Alias Path",
        name: "useRenderAlias",
        type: "boolean",
        default: false,
        displayOptions: {
          show: { resource: ["render"], operation: ["list"] },
        },
        description: "If true, call GET /api/v1/render instead of /api/v1/jobs",
      },
      {
        displayName: "Job ID",
        name: "jobId",
        type: "string",
        required: true,
        default: "",
        displayOptions: {
          show: { resource: ["render"], operation: ["get"] },
        },
      },
      {
        displayName: "Limit",
        name: "limit",
        type: "number",
        typeOptions: { minValue: 1, maxValue: 100 },
        default: 20,
        displayOptions: {
          show: { resource: ["render"], operation: ["list"] },
        },
        description: "Max jobs to return (default 20, max 100)",
      },
      {
        displayName: "Image Path",
        name: "imagePath",
        type: "string",
        required: true,
        default: "",
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
        description: "Server path from Upload (type=image)",
      },
      {
        displayName: "Audio Path",
        name: "audioPath",
        type: "string",
        required: true,
        default: "",
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
        description: "Server path from Upload (type=audio)",
      },
      {
        displayName: "Audio Filename",
        name: "audioFilename",
        type: "string",
        required: true,
        default: "",
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
      },
      {
        displayName: "YouTube Title",
        name: "title",
        type: "string",
        default: "",
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
        description:
          "YouTube video title. Falls back to artist - songTitle when empty.",
      },
      {
        displayName: "Privacy",
        name: "privacy",
        type: "options",
        options: PRIVACY.map((p) => ({ name: p, value: p })),
        default: "UNLISTED",
        required: true,
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
      },
      {
        displayName: "Resolution",
        name: "resolution",
        type: "options",
        options: RESOLUTIONS.map((r) => ({ name: r, value: r })),
        default: "1920x1080",
        required: true,
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
      },
      {
        displayName: "Additional Fields",
        name: "additionalFields",
        type: "collection",
        placeholder: "Add Field",
        default: {},
        displayOptions: {
          show: {
            resource: ["render"],
            operation: ["create", "createJob"],
          },
        },
        options: [
          {
            displayName: "Webhook URL",
            name: "webhookUrl",
            type: "string",
            default: "",
            description:
              "Absolute https URL — Songs2VID POSTs job.item.* / job.* events",
          },
          {
            displayName: "Song Title (on-video)",
            name: "songTitle",
            type: "string",
            default: "",
            description: "Art-track on-video title (max 120)",
          },
          {
            displayName: "Artist (on-video)",
            name: "artist",
            type: "string",
            default: "",
            description: "Art-track on-video artist (max 80)",
          },
          {
            displayName: "Description",
            name: "description",
            type: "string",
            default: "",
            typeOptions: { rows: 3 },
          },
          {
            displayName: "Tags",
            name: "tags",
            type: "string",
            default: "",
            description: "Comma-separated YouTube tags",
          },
          {
            displayName: "Category ID",
            name: "categoryId",
            type: "string",
            default: "10",
            description: "YouTube category (10 = Music)",
          },
          {
            displayName: "Notify Subscribers",
            name: "notifySubscribers",
            type: "boolean",
            default: false,
          },
          {
            displayName: "Made for Kids",
            name: "madeForKids",
            type: "boolean",
            default: false,
          },
          {
            displayName: "Embeddable",
            name: "embeddable",
            type: "boolean",
            default: true,
          },
          {
            displayName: "Creative Commons",
            name: "creativeCommons",
            type: "boolean",
            default: false,
          },
          {
            displayName: "Include Watermark",
            name: "includeWatermark",
            type: "boolean",
            default: false,
          },
          {
            displayName: "Per-track Cover Path",
            name: "itemImagePath",
            type: "string",
            default: "",
            description: "Overrides job imagePath for this track",
          },
          {
            displayName: "Background Image Path",
            name: "backgroundImagePath",
            type: "string",
            default: "",
            description: "Lower-corner templates only",
          },
          {
            displayName: "Playlist ID",
            name: "playlistId",
            type: "string",
            default: "",
          },
          {
            displayName: "Create Playlist Title",
            name: "createPlaylistTitle",
            type: "string",
            default: "",
            description: "If set, creates a playlist and attaches all videos",
          },
          {
            displayName: "Create Playlist Description",
            name: "createPlaylistDescription",
            type: "string",
            default: "",
          },
          {
            displayName: "Create Playlist Privacy",
            name: "createPlaylistPrivacy",
            type: "options",
            options: [
              { name: "public", value: "public" },
              { name: "unlisted", value: "unlisted" },
              { name: "private", value: "private" },
            ],
            default: "private",
          },
          {
            displayName: "Layout Template",
            name: "layoutTemplate",
            type: "options",
            options: [
              { name: "(classic letterbox)", value: "" },
              ...LAYOUT_TEMPLATES.map((t) => ({ name: t, value: t })),
            ],
            default: "",
          },
          {
            displayName: "Blur Amount",
            name: "blurAmount",
            type: "number",
            typeOptions: { minValue: 0, maxValue: 100 },
            default: 55,
          },
          {
            displayName: "Blur Opacity",
            name: "blurOpacity",
            type: "number",
            typeOptions: { minValue: 0, maxValue: 100 },
            default: 100,
          },
          {
            displayName: "Blur Fill (letterbox)",
            name: "blurFill",
            type: "boolean",
            default: false,
          },
          {
            displayName: "Text Padding",
            name: "textPadding",
            type: "number",
            typeOptions: { minValue: 16, maxValue: 120 },
            default: 48,
          },
          {
            displayName: "Title / Artist Gap",
            name: "titleArtistGap",
            type: "number",
            typeOptions: { minValue: 0, maxValue: 64 },
            default: 10,
          },
          {
            displayName: "Title Bold",
            name: "titleBold",
            type: "boolean",
            default: true,
          },
          {
            displayName: "Text Offset X",
            name: "textOffsetX",
            type: "number",
            typeOptions: { minValue: -120, maxValue: 120 },
            default: 0,
          },
          {
            displayName: "Text Offset Y",
            name: "textOffsetY",
            type: "number",
            typeOptions: { minValue: -120, maxValue: 120 },
            default: 0,
          },
          {
            displayName: "Watermark Mode",
            name: "watermarkMode",
            type: "options",
            options: WATERMARK_MODES.map((m) => ({ name: m, value: m })),
            default: "none",
          },
          {
            displayName: "Watermark Text",
            name: "watermarkText",
            type: "string",
            default: "",
          },
          {
            displayName: "Watermark Logo Path",
            name: "watermarkLogoPath",
            type: "string",
            default: "",
          },
          {
            displayName: "Watermark Position",
            name: "watermarkPosition",
            type: "options",
            options: WATERMARK_POSITIONS.map((p) => ({ name: p, value: p })),
            default: "bottom-right",
          },
          {
            displayName: "Watermark Offset X",
            name: "watermarkOffsetX",
            type: "number",
            typeOptions: { minValue: 0, maxValue: 200 },
            default: 20,
          },
          {
            displayName: "Watermark Offset Y",
            name: "watermarkOffsetY",
            type: "number",
            typeOptions: { minValue: 0, maxValue: 200 },
            default: 20,
          },
          {
            displayName: "Font Key",
            name: "fontKey",
            type: "options",
            options: FONT_KEYS.map((k) => ({ name: k, value: k })),
            default: "montserrat",
          },
          {
            displayName: "Custom Font Path",
            name: "fontPath",
            type: "string",
            default: "",
            description: "Required when Font Key = custom",
          },
          {
            displayName: "Extra Items JSON",
            name: "extraItemsJson",
            type: "json",
            default: "[]",
            description:
              "Optional array of additional { audioPath, audioFilename, metadata } objects for multi-track jobs",
          },
        ],
      },

      // Batch fields
      {
        displayName: "Cover Binary Property",
        name: "batchImageBinary",
        type: "string",
        default: "cover",
        required: true,
        displayOptions: {
          show: { resource: ["render"], operation: ["createBatch"] },
        },
      },
      {
        displayName: "Audio Binary Property",
        name: "batchAudioBinary",
        type: "string",
        default: "audio",
        required: true,
        displayOptions: {
          show: { resource: ["render"], operation: ["createBatch"] },
        },
        description:
          "Binary field name for audio. For multiple files, pass items with this property or use Audio Binary Properties list.",
      },
      {
        displayName: "Additional Fields",
        name: "batchAdditionalFields",
        type: "collection",
        placeholder: "Add Field",
        default: {},
        displayOptions: {
          show: { resource: ["render"], operation: ["createBatch"] },
        },
        options: [
          {
            displayName: "Metadata JSON",
            name: "metadataJson",
            type: "json",
            default:
              '{"defaults":{"privacy":"PUBLIC","resolution":"1920x1080"},"items":[]}',
            description:
              "Multipart metadata string: { defaults?, items?, createPlaylist? }",
          },
        ],
      },

      // ── Playlist ───────────────────────────────────────────────
      {
        displayName: "Operation",
        name: "operation",
        type: "options",
        noDataExpression: true,
        displayOptions: { show: { resource: ["playlist"] } },
        options: [
          {
            name: "List",
            value: "list",
            description: "GET /api/v1/playlists",
            action: "List playlists",
          },
          {
            name: "Create",
            value: "create",
            description: "POST /api/v1/playlists",
            action: "Create a playlist",
          },
        ],
        default: "list",
      },
      {
        displayName: "Title",
        name: "playlistTitle",
        type: "string",
        required: true,
        default: "",
        displayOptions: {
          show: { resource: ["playlist"], operation: ["create"] },
        },
      },
      {
        displayName: "Additional Fields",
        name: "playlistAdditionalFields",
        type: "collection",
        placeholder: "Add Field",
        default: {},
        displayOptions: {
          show: { resource: ["playlist"], operation: ["create"] },
        },
        options: [
          {
            displayName: "Description",
            name: "description",
            type: "string",
            default: "",
          },
          {
            displayName: "Privacy",
            name: "privacy",
            type: "options",
            options: [
              { name: "public", value: "public" },
              { name: "unlisted", value: "unlisted" },
              { name: "private", value: "private" },
            ],
            default: "private",
          },
        ],
      },

      // ── API Key ────────────────────────────────────────────────
      {
        displayName: "Operation",
        name: "operation",
        type: "options",
        noDataExpression: true,
        displayOptions: { show: { resource: ["apiKey"] } },
        options: [
          {
            name: "List",
            value: "list",
            description: "GET /api/v1/user/api-keys",
            action: "List API keys",
          },
          {
            name: "Create",
            value: "create",
            description: "POST /api/v1/user/api-keys",
            action: "Create an API key",
          },
          {
            name: "Delete",
            value: "delete",
            description: "DELETE /api/v1/user/api-keys",
            action: "Delete API key(s)",
          },
        ],
        default: "list",
      },
      {
        displayName: "Name",
        name: "apiKeyName",
        type: "string",
        default: "Default",
        displayOptions: {
          show: { resource: ["apiKey"], operation: ["create"] },
        },
        description: "Optional label (max 64)",
      },
      {
        displayName: "Additional Fields",
        name: "apiKeyAdditionalFields",
        type: "collection",
        placeholder: "Add Field",
        default: {},
        displayOptions: {
          show: { resource: ["apiKey"], operation: ["delete"] },
        },
        options: [
          {
            displayName: "Key ID",
            name: "id",
            type: "string",
            default: "",
            description: "Omit to revoke all keys",
          },
        ],
      },
    ],
  };

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const returnData: INodeExecutionData[] = [];
    const resource = this.getNodeParameter("resource", 0) as string;
    const operation = this.getNodeParameter("operation", 0) as string;

    const credentials = await this.getCredentials("songs2VidApi").catch(
      () => null,
    );
    const baseUrl = String(
      credentials?.baseUrl || "https://songs2vid.com",
    ).replace(/\/$/, "");

    for (let i = 0; i < items.length; i++) {
      try {
        let response: IDataObject | IDataObject[] | undefined;

        if (resource === "discovery" && operation === "getCatalog") {
          response = (await this.helpers.request({
            method: "GET",
            url: `${baseUrl}/api/v1`,
            json: true,
          } as IRequestOptions)) as IDataObject;
        } else if (resource === "file" && operation === "upload") {
          const binaryPropertyName = this.getNodeParameter(
            "binaryPropertyName",
            i,
          ) as string;
          const uploadType = this.getNodeParameter("uploadType", i) as string;
          const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
          const buffer = await this.helpers.getBinaryDataBuffer(
            i,
            binaryPropertyName,
          );

          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "POST",
              url: `${baseUrl}/api/v1/upload`,
              formData: {
                file: {
                  value: buffer,
                  options: {
                    filename: binaryData.fileName || "upload.bin",
                    contentType: binaryData.mimeType || "application/octet-stream",
                  },
                },
                type: uploadType,
              },
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (
          resource === "render" &&
          (operation === "create" || operation === "createJob")
        ) {
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "POST",
              url: `${baseUrl}/api/v1/${operation === "create" ? "render" : "jobs"}`,
              body: buildCreateJobBody(this, i),
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "render" && operation === "get") {
          const jobId = this.getNodeParameter("jobId", i) as string;
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "GET",
              url: `${baseUrl}/api/v1/jobs/${encodeURIComponent(jobId)}`,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "render" && operation === "list") {
          const limit = this.getNodeParameter("limit", i) as number;
          const useAlias = this.getNodeParameter(
            "useRenderAlias",
            i,
            false,
          ) as boolean;
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "GET",
              url: `${baseUrl}/api/v1/${useAlias ? "render" : "jobs"}`,
              qs: { limit },
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "render" && operation === "createBatch") {
          const imageProp = this.getNodeParameter(
            "batchImageBinary",
            i,
          ) as string;
          const audioProp = this.getNodeParameter(
            "batchAudioBinary",
            i,
          ) as string;
          const extra = this.getNodeParameter(
            "batchAdditionalFields",
            i,
            {},
          ) as IDataObject;

          const imageBin = this.helpers.assertBinaryData(i, imageProp);
          const imageBuf = await this.helpers.getBinaryDataBuffer(i, imageProp);
          const audioBin = this.helpers.assertBinaryData(i, audioProp);
          const audioBuf = await this.helpers.getBinaryDataBuffer(i, audioProp);

          const formData: IDataObject = {
            image: {
              value: imageBuf,
              options: {
                filename: imageBin.fileName || "cover.jpg",
                contentType: imageBin.mimeType || "image/jpeg",
              },
            },
            audio: {
              value: audioBuf,
              options: {
                filename: audioBin.fileName || "track.mp3",
                contentType: audioBin.mimeType || "audio/mpeg",
              },
            },
          };

          if (extra.metadataJson) {
            formData.metadata =
              typeof extra.metadataJson === "string"
                ? extra.metadataJson
                : JSON.stringify(extra.metadataJson);
          }

          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "POST",
              url: `${baseUrl}/api/v1/jobs/batch`,
              formData,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "playlist" && operation === "list") {
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "GET",
              url: `${baseUrl}/api/v1/playlists`,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "playlist" && operation === "create") {
          const title = this.getNodeParameter("playlistTitle", i) as string;
          const extra = this.getNodeParameter(
            "playlistAdditionalFields",
            i,
            {},
          ) as IDataObject;
          const body: IDataObject = { title };
          if (extra.description) body.description = extra.description;
          if (extra.privacy) body.privacy = extra.privacy;
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "POST",
              url: `${baseUrl}/api/v1/playlists`,
              body,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "apiKey" && operation === "list") {
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "GET",
              url: `${baseUrl}/api/v1/user/api-keys`,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "apiKey" && operation === "create") {
          const name = this.getNodeParameter("apiKeyName", i) as string;
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "POST",
              url: `${baseUrl}/api/v1/user/api-keys`,
              body: { name },
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else if (resource === "apiKey" && operation === "delete") {
          const extra = this.getNodeParameter(
            "apiKeyAdditionalFields",
            i,
            {},
          ) as IDataObject;
          const qs: IDataObject = {};
          if (extra.id) qs.id = extra.id;
          response = (await this.helpers.requestWithAuthentication.call(
            this,
            "songs2VidApi",
            {
              method: "DELETE",
              url: `${baseUrl}/api/v1/user/api-keys`,
              qs,
              json: true,
            } as IRequestOptions,
          )) as IDataObject;
        } else {
          throw new NodeOperationError(
            this.getNode(),
            `Unknown resource/operation: ${resource}/${operation}`,
            { itemIndex: i },
          );
        }

        const executionData = this.helpers.constructExecutionMetaData(
          this.helpers.returnJsonArray(response as IDataObject),
          { itemData: { item: i } },
        );
        returnData.push(...executionData);
      } catch (error) {
        if (this.continueOnFail()) {
          returnData.push({
            json: {
              error: error instanceof Error ? error.message : String(error),
            },
            pairedItem: { item: i },
          });
          continue;
        }
        throw error;
      }
    }

    return [returnData];
  }
}

function buildCreateJobBody(
  ctx: IExecuteFunctions,
  itemIndex: number,
): IDataObject {
  const imagePath = ctx.getNodeParameter("imagePath", itemIndex) as string;
  const audioPath = ctx.getNodeParameter("audioPath", itemIndex) as string;
  const audioFilename = ctx.getNodeParameter(
    "audioFilename",
    itemIndex,
  ) as string;
  const title = ctx.getNodeParameter("title", itemIndex) as string;
  const privacy = ctx.getNodeParameter("privacy", itemIndex) as string;
  const resolution = ctx.getNodeParameter("resolution", itemIndex) as string;
  const extra = ctx.getNodeParameter(
    "additionalFields",
    itemIndex,
    {},
  ) as IDataObject;

  const metadata: IDataObject = {
    title,
    privacy,
    resolution,
  };

  const passthrough = [
    "songTitle",
    "artist",
    "description",
    "tags",
    "categoryId",
    "notifySubscribers",
    "madeForKids",
    "embeddable",
    "creativeCommons",
    "includeWatermark",
    "backgroundImagePath",
    "playlistId",
  ] as const;

  for (const key of passthrough) {
    const value = extra[key];
    if (value !== undefined && value !== null && value !== "") {
      metadata[key] = value;
    }
  }
  if (extra.itemImagePath) {
    metadata.imagePath = extra.itemImagePath;
  }

  if (extra.layoutTemplate) {
    metadata.layout = {
      template: extra.layoutTemplate,
      blurAmount: extra.blurAmount ?? 55,
      blurOpacity: extra.blurOpacity ?? 100,
      blurFill: extra.blurFill ?? false,
      textPadding: extra.textPadding ?? 48,
      titleArtistGap: extra.titleArtistGap ?? 10,
      titleBold: extra.titleBold ?? true,
      textOffsetX: extra.textOffsetX ?? 0,
      textOffsetY: extra.textOffsetY ?? 0,
    };
  } else if (extra.blurFill === true) {
    metadata.layout = {
      blurFill: true,
      blurAmount: extra.blurAmount ?? 55,
      blurOpacity: extra.blurOpacity ?? 100,
    };
  }

  if (
    extra.watermarkMode ||
    extra.watermarkText ||
    extra.watermarkLogoPath ||
    extra.fontKey
  ) {
    const watermark: IDataObject = {
      mode: (extra.watermarkMode as string) || "none",
      position: (extra.watermarkPosition as string) || "bottom-right",
      offsetX: extra.watermarkOffsetX ?? 20,
      offsetY: extra.watermarkOffsetY ?? 20,
      fontKey: (extra.fontKey as string) || "montserrat",
    };
    if (extra.watermarkText) watermark.text = extra.watermarkText;
    if (extra.watermarkLogoPath) watermark.logoPath = extra.watermarkLogoPath;
    if (extra.fontPath) watermark.fontPath = extra.fontPath;
    metadata.watermark = watermark;
  }

  const items: IDataObject[] = [
    {
      audioPath,
      audioFilename,
      metadata,
    },
  ];

  if (extra.extraItemsJson) {
    const parsed =
      typeof extra.extraItemsJson === "string"
        ? (JSON.parse(extra.extraItemsJson) as IDataObject[])
        : (extra.extraItemsJson as IDataObject[]);
    if (Array.isArray(parsed)) {
      items.push(...parsed);
    }
  }

  const body: IDataObject = {
    imagePath,
    items,
  };

  if (extra.webhookUrl) {
    body.webhookUrl = extra.webhookUrl;
  }

  if (extra.createPlaylistTitle) {
    body.createPlaylist = {
      title: extra.createPlaylistTitle,
      description: extra.createPlaylistDescription || undefined,
      privacy: extra.createPlaylistPrivacy || "private",
    };
  }

  return body;
}
