'use strict';

const { ComponentBuilder } = require('@discordjs/builders');
const { ComponentType } = require('discord-api-types/v10');

/**
 * @typedef {Object} BaseComponentData
 * @property {number} [id] the id of this component
 * @property {ComponentType} type The type of component
 */

/**
 * @typedef {BaseComponentData} ActionRowData
 * @property {ComponentData[]} components The components in this action row
 */

/**
 * @typedef {Object} ModalComponentData
 * @property {string} title The title of the modal
 * @property {string} customId The custom id of the modal
 * @property {Array<ActionRow|TextDisplayComponentData|LabelData>} components The components within this modal
 */

/**
 * @typedef {StringSelectMenuComponentData|TextInputComponentData|UserSelectMenuComponentData|
 * RoleSelectMenuComponentData|MentionableSelectMenuComponentData|ChannelSelectMenuComponentData|
 * FileUploadComponentData|RadioGroupComponentData|CheckboxGroupComponentData|
 * CheckboxComponentData} ComponentInLabelData
 */

/**
 * @typedef {BaseComponentData} LabelData
 * @property {string} label The label to use
 * @property {string} [description] The optional description for the label
 * @property {ComponentInLabelData} component The component within the label
 */

/**
 * @typedef {BaseComponentData} ButtonComponentData
 * @property {ButtonStyle} style The style of the button
 * @property {boolean} [disabled] Whether this button is disabled
 * @property {string} label The label of this button
 * @property {APIMessageComponentEmoji} [emoji] The emoji on this button
 * @property {string} [customId] The custom id of the button
 * @property {string} [url] The URL of the button
 */

/**
 * @typedef {BaseComponentData} FileUploadComponentData
 * @property {string} customId The custom id of the file upload
 * @property {number} [minValues] The minimum number of files that can be uploaded (0-10)
 * @property {number} [maxValues] The maximum number of files that can be uploaded (1-10)
 * @property {boolean} [required] Whether this component is required in modals
 */

/**
 * @typedef {Object} RadioGroupOption
 * @property {string} value The value of the radio group option
 * @property {string} label The label to use
 * @property {string} [description] The optional description for the radio group option
 * @property {boolean} [default] Whether this option is default selected
 */

/**
 * @typedef {BaseComponentData} RadioGroupComponentData
 * @property {string} customId The custom id of the radio group
 * @property {RadioGroupOption[]} options The options in this radio group (2-10)
 * @property {boolean} [required] Whether this component is required in modals
 */

/**
 * @typedef {Object} CheckboxGroupOption
 * @property {string} value The value of the checkbox group option
 * @property {string} label The label to use
 * @property {string} [description] The optional description for the checkbox group option
 * @property {boolean} [default] Whether this option is default selected
 */

/**
 * @typedef {BaseComponentData} CheckboxGroupComponentData
 * @property {string} customId The custom id of the checkbox group
 * @property {CheckboxGroupOption[]} options The options in this checkbox group
 * @property {number} [minValues] The minimum number of options that must be selected (0-10)
 * @property {number} [maxValues] The maximum number of options that can be selected (defaults to options length)
 * @property {boolean} [required] Whether this component is required in modals
 */

/**
 * @typedef {BaseComponentData} CheckboxComponentData
 * @property {string} customId The custom id of the checkbox
 * @property {boolean} [default] Whether this component is default selected in modals
 */

/**
 * @typedef {BaseComponentData} BaseSelectMenuComponentData
 * @property {string} customId The custom id of the select menu
 * @property {boolean} [disabled] Whether the select menu is disabled or not
 * @property {number} [maxValues] The maximum amount of options that can be selected
 * @property {number} [minValues] The minimum amount of options that can be selected
 * @property {string} [placeholder] The placeholder of the select menu
 * @property {boolean} [required] Whether this component is required in modals
 */

/**
 * @typedef {BaseSelectMenuComponentData} StringSelectMenuComponentData
 * @property {SelectMenuComponentOptionData[]} [options] The options in this select menu
 */

/**
 * @typedef {BaseSelectMenuComponentData} UserSelectMenuComponentData
 * @property {APISelectMenuDefaultValue[]} [defaultValues] The default selected values in this select menu
 */

/**
 * @typedef {BaseSelectMenuComponentData} RoleSelectMenuComponentData
 * @property {APISelectMenuDefaultValue[]} [defaultValues] The default selected values in this select menu
 */

/**
 * @typedef {BaseSelectMenuComponentData} MentionableSelectMenuComponentData
 * @property {APISelectMenuDefaultValue[]} [defaultValues] The default selected values in this select menu
 */

/**
 * @typedef {BaseSelectMenuComponentData} ChannelSelectMenuComponentData
 * @property {APISelectMenuDefaultValue[]} [defaultValues] The default selected values in this select menu
 * @property {ChannelType[]} [channelTypes] The types of channels that can be selected
 */

/**
 * @typedef {object} SelectMenuComponentOptionData
 * @property {string} label The label of the option
 * @property {string} value The value of the option
 * @property {string} [description] The description of the option
 * @property {APIMessageComponentEmoji} [emoji] The emoji on the option
 * @property {boolean} [default] Whether this option is selected by default
 */

/**
 * @typedef {BaseComponentData} SelectMenuComponentData
 * @property {string} customId The custom id of the select menu
 * @property {boolean} [disabled] Whether the select menu is disabled or not
 * @property {number} [maxValues] The maximum amount of options that can be selected
 * @property {number} [minValues] The minimum amount of options that can be selected
 * @property {SelectMenuComponentOptionData[]} [options] The options in this select menu
 * @property {string} [placeholder] The placeholder of the select menu
 */

/**
 * @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} MessageComponentData
 */

/**
 * @typedef {BaseComponentData} TextInputComponentData
 * @property {string} customId The custom id of the text input
 * @property {TextInputStyle} style The style of the text input
 * @property {string} label The text that appears on top of the text input field
 * @property {number} [minLength] The minimum number of characters that can be entered in the text input
 * @property {number} [maxLength] The maximum number of characters that can be entered in the text input
 * @property {boolean} [required] Whether or not the text input is required or not
 * @property {string} [value] The pre-filled text in the text input
 * @property {string} [placeholder] Placeholder for the text input
 */

/**
 * @typedef {Object} UnfurledMediaItemData
 * @property {string} url The url of this media item. Accepts either http:, https: or attachment: protocol
 */

/**
 * @typedef {BaseComponentData} ThumbnailComponentData
 * @property {UnfurledMediaItemData} media The media for the thumbnail
 * @property {string} [description] The description of the thumbnail
 * @property {boolean} [spoiler] Whether the thumbnail should be spoilered
 */

/**
 * @typedef {BaseComponentData} FileComponentData
 * @property {UnfurledMediaItemData} file The file media in this component
 * @property {boolean} [spoiler] Whether the file should be spoilered
 */

/**
 * @typedef {Object} MediaGalleryItemData
 * @property {UnfurledMediaItemData} media The media for the media gallery item
 * @property {string} [description] The description of the media gallery item
 * @property {boolean} [spoiler] Whether the media gallery item should be spoilered
 */

/**
 * @typedef {BaseComponentData} MediaGalleryComponentData
 * @property {MediaGalleryItemData[]} items The media gallery items in this media gallery component
 */

/**
 * @typedef {BaseComponentData} SeparatorComponentData
 * @property {SeparatorSpacingSize} [spacing] The spacing size of this component
 * @property {boolean} [divider] Whether the separator shows as a divider
 */

/**
 * @typedef {BaseComponentData} SectionComponentData
 * @property {Components[]} components The components in this section
 * @property {ButtonComponentData|ThumbnailComponentData} accessory The accessory shown next to this section
 */

/**
 * @typedef {BaseComponentData} TextDisplayComponentData
 * @property {string} content The content displayed in this component
 */

/**
 * @typedef {ActionRowData|FileComponentData|MediaGalleryComponentData|SectionComponentData|
 * SeparatorComponentData|TextDisplayComponentData} ComponentInContainerData
 */

/**
 * @typedef {BaseComponentData} ContainerComponentData
 * @property {ComponentInContainerData} components The components in this container
 * @property {?number} [accentColor] The accent color of this container
 * @property {boolean} [spoiler] Whether the container should be spoilered
 */

/**
 * @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData|
 * ThumbnailComponentData|FileComponentData|MediaGalleryComponentData|SeparatorComponentData|
 * SectionComponentData|TextDisplayComponentData|ContainerComponentData} ComponentData
 */

/**
 * Any emoji data that can be used within a button
 * @typedef {APIMessageComponentEmoji|string} ComponentEmojiResolvable
 */

/**
 * @typedef {ActionRow|ContainerComponent|FileComponent|MediaGalleryComponent|
 * SectionComponent|SeparatorComponent|TextDisplayComponent} MessageTopLevelComponent
 */

/**
 * Transforms API data into a component
 * @param {APIMessageComponent|Component} data The data to create the component from
 * @returns {Component}
 * @ignore
 */
function createComponent(data) {
  return data instanceof Component ? data : new (ComponentTypeToComponent[data.type] ?? Component)(data);
}

/**
 * Transforms API data into a component builder
 * @param {APIMessageComponent|ComponentBuilder} data The data to create the component from
 * @returns {ComponentBuilder}
 * @ignore
 */
function createComponentBuilder(data) {
  return data instanceof ComponentBuilder ? data : new (ComponentTypeToBuilder[data.type] ?? ComponentBuilder)(data);
}

/**
 * Extracts all interactive components from the component tree
 * @param {Component|APIMessageComponent} component The component to find all interactive components in
 * @returns {Array<Component|APIMessageComponent>}
 * @ignore
 */
function extractInteractiveComponents(component) {
  switch (component.type) {
    case ComponentType.ActionRow:
      return component.components;
    case ComponentType.Section:
      return [...component.components, component.accessory];
    case ComponentType.Container:
      return component.components.flatMap(extractInteractiveComponents);
    default:
      return [component];
  }
}

/**
 * Finds a component by customId in nested components
 * @param {Array<Component|APIMessageComponent>} components The components to search in
 * @param {string} customId The customId to search for
 * @returns {Component|APIMessageComponent}
 * @ignore
 */
function findComponentByCustomId(components, customId) {
  return (
    components
      .flatMap(extractInteractiveComponents)
      .find(component => (component.customId ?? component.custom_id) === customId) ?? null
  );
}

module.exports = { createComponent, createComponentBuilder, findComponentByCustomId };

const ActionRow = require('../structures/ActionRow');
const ActionRowBuilder = require('../structures/ActionRowBuilder');
const ButtonBuilder = require('../structures/ButtonBuilder');
const ButtonComponent = require('../structures/ButtonComponent');
const ChannelSelectMenuBuilder = require('../structures/ChannelSelectMenuBuilder');
const ChannelSelectMenuComponent = require('../structures/ChannelSelectMenuComponent');
const Component = require('../structures/Component');
const ContainerComponent = require('../structures/ContainerComponent');
const FileComponent = require('../structures/FileComponent');
const LabelComponent = require('../structures/LabelComponent');
const MediaGalleryComponent = require('../structures/MediaGalleryComponent');
const MentionableSelectMenuBuilder = require('../structures/MentionableSelectMenuBuilder');
const MentionableSelectMenuComponent = require('../structures/MentionableSelectMenuComponent');
const RoleSelectMenuBuilder = require('../structures/RoleSelectMenuBuilder');
const RoleSelectMenuComponent = require('../structures/RoleSelectMenuComponent');
const SectionComponent = require('../structures/SectionComponent');
const SeparatorComponent = require('../structures/SeparatorComponent');
const StringSelectMenuBuilder = require('../structures/StringSelectMenuBuilder');
const StringSelectMenuComponent = require('../structures/StringSelectMenuComponent');
const TextDisplayComponent = require('../structures/TextDisplayComponent');
const TextInputBuilder = require('../structures/TextInputBuilder');
const TextInputComponent = require('../structures/TextInputComponent');
const ThumbnailComponent = require('../structures/ThumbnailComponent');
const UserSelectMenuBuilder = require('../structures/UserSelectMenuBuilder');
const UserSelectMenuComponent = require('../structures/UserSelectMenuComponent');

const ComponentTypeToComponent = {
  [ComponentType.ActionRow]: ActionRow,
  [ComponentType.Button]: ButtonComponent,
  [ComponentType.StringSelect]: StringSelectMenuComponent,
  [ComponentType.TextInput]: TextInputComponent,
  [ComponentType.UserSelect]: UserSelectMenuComponent,
  [ComponentType.RoleSelect]: RoleSelectMenuComponent,
  [ComponentType.MentionableSelect]: MentionableSelectMenuComponent,
  [ComponentType.ChannelSelect]: ChannelSelectMenuComponent,
  [ComponentType.Container]: ContainerComponent,
  [ComponentType.TextDisplay]: TextDisplayComponent,
  [ComponentType.File]: FileComponent,
  [ComponentType.MediaGallery]: MediaGalleryComponent,
  [ComponentType.Section]: SectionComponent,
  [ComponentType.Separator]: SeparatorComponent,
  [ComponentType.Thumbnail]: ThumbnailComponent,
  [ComponentType.Label]: LabelComponent,
};

const ComponentTypeToBuilder = {
  [ComponentType.ActionRow]: ActionRowBuilder,
  [ComponentType.Button]: ButtonBuilder,
  [ComponentType.StringSelect]: StringSelectMenuBuilder,
  [ComponentType.TextInput]: TextInputBuilder,
  [ComponentType.UserSelect]: UserSelectMenuBuilder,
  [ComponentType.RoleSelect]: RoleSelectMenuBuilder,
  [ComponentType.MentionableSelect]: MentionableSelectMenuBuilder,
  [ComponentType.ChannelSelect]: ChannelSelectMenuBuilder,
};
