'use client';

import {
  ComponentActionTypeSchema,
  ComponentButtonStyleSchema,
  emptyComponentsV2,
  MessageComponentsV2Schema,
  parseMessageComponentsV2,
  type ComponentAction,
  type ComponentActionRow,
  type ComponentActionType,
  type ComponentButton,
  type ComponentButtonStyle,
  type ComponentSelect,
  type ComponentStringSelect,
  type ComponentV2Node,
  type MessageComponentsV2,
  type StringSelectOption
} from '@nexumi/shared';
import { ChevronDown, ChevronUp, Plus, Trash2 } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslations } from '@/components/locale-provider';
import { Button } from '@/components/ui/button';
import { DiscordRoleSelect } from '@/components/ui/discord-role-select';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { PlaceholderHelp } from '@/components/ui/placeholder-help';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { Textarea } from '@/components/ui/textarea';
import type { PlaceholderPresetId } from '@/lib/placeholder-presets';
import { cn } from '@/lib/utils';

export { emptyComponentsV2 };

const ACTION_TYPES = ComponentActionTypeSchema.options;
const BUTTON_STYLES = ComponentButtonStyleSchema.options;
const SELECT_TYPES = [
  'string_select',
  'user_select',
  'role_select',
  'channel_select',
  'mentionable_select'
] as const;

type SelectType = (typeof SELECT_TYPES)[number];
type BlockType = ComponentV2Node['type'];

function newActionId(prefix = 'action'): string {
  return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
}

function applyPreviewVars(template: string, vars: Record<string, string>): string {
  const withVars = Object.entries(vars).reduce(
    (acc, [key, value]) => acc.replaceAll(`{${key}}`, value),
    template
  );
  return withVars.replace(/\["(\d{17,20})"\]/g, '#$1').replace(/<#(\d{17,20})>/g, '#$1');
}

function previewUrl(value: string | undefined, vars: Record<string, string>): string | undefined {
  if (!value?.trim()) {
    return undefined;
  }
  const rendered = applyPreviewVars(value, vars).trim();
  if (!/^https?:\/\//i.test(rendered)) {
    return undefined;
  }
  return rendered;
}

export function normalizeComponentsV2(
  value: MessageComponentsV2 | null | undefined
): MessageComponentsV2 | null {
  if (!value) {
    return null;
  }
  const parsed = MessageComponentsV2Schema.safeParse(value);
  return parsed.success ? parsed.data : null;
}

export function componentsV2FromUnknown(raw: unknown): MessageComponentsV2 | null {
  return parseMessageComponentsV2(raw);
}

function defaultBlock(type: BlockType): ComponentV2Node {
  switch (type) {
    case 'container':
      return { type: 'container', components: [{ type: 'text_display', content: 'Hello from Nexumi!' }] };
    case 'text_display':
      return { type: 'text_display', content: 'Text content' };
    case 'separator':
      return { type: 'separator', divider: true, spacing: 'Small' };
    case 'section':
      return {
        type: 'section',
        text: 'Section text',
        accessory: { type: 'thumbnail', url: 'https://cdn.discordapp.com/embed/avatars/0.png' }
      };
    case 'media_gallery':
      return {
        type: 'media_gallery',
        items: [{ url: 'https://cdn.discordapp.com/embed/avatars/0.png' }]
      };
    case 'action_row':
      return {
        type: 'action_row',
        components: [{ type: 'button', style: 'Secondary', label: 'Button' }]
      };
    default:
      return { type: 'text_display', content: 'Text content' };
  }
}

function defaultSelect(type: SelectType): ComponentSelect {
  switch (type) {
    case 'string_select':
      return {
        type: 'string_select',
        placeholder: 'Choose an option',
        options: [{ label: 'Option 1', value: 'opt1' }]
      };
    case 'user_select':
      return { type: 'user_select', actionId: newActionId('user'), placeholder: 'Select a user' };
    case 'role_select':
      return {
        type: 'role_select',
        actionId: newActionId('role'),
        placeholder: 'Select roles',
        maxValues: 5
      };
    case 'channel_select':
      return { type: 'channel_select', actionId: newActionId('channel'), placeholder: 'Select a channel' };
    case 'mentionable_select':
      return {
        type: 'mentionable_select',
        actionId: newActionId('mentionable'),
        placeholder: 'Select a user or role'
      };
  }
}

function defaultAction(type: ComponentActionType): ComponentAction {
  switch (type) {
    case 'EPHEMERAL_REPLY':
    case 'PUBLIC_REPLY':
      return { type, content: 'Thanks for clicking!' };
    case 'ASSIGN_ROLE':
    case 'REMOVE_ROLE':
    case 'TOGGLE_ROLE':
      return { type, roleId: '' };
    case 'ASSIGN_SELECTED_ROLES':
      return { type };
    case 'NONE':
      return { type };
  }
}

function ensureAction(
  actions: MessageComponentsV2['actions'],
  actionId: string,
  fallback: ComponentAction = { type: 'NONE' }
): MessageComponentsV2['actions'] {
  if (actions[actionId]) {
    return actions;
  }
  return { ...actions, [actionId]: fallback };
}

interface DiscordComponentsV2BuilderProps {
  value: MessageComponentsV2 | null;
  onChange: (next: MessageComponentsV2 | null) => void;
  guildId?: string;
  className?: string;
  previewVars?: Record<string, string>;
  showClearHint?: boolean;
  placeholderPreset?: PlaceholderPresetId;
}

export function DiscordComponentsV2Builder({
  value,
  onChange,
  guildId,
  className,
  previewVars,
  showClearHint = true,
  placeholderPreset = 'welcome'
}: DiscordComponentsV2BuilderProps) {
  const t = useTranslations();
  const draft = value ?? emptyComponentsV2();
  const vars = previewVars ?? {};

  function patch(next: Partial<MessageComponentsV2>) {
    onChange({ ...draft, ...next });
  }

  function patchComponents(components: ComponentV2Node[]) {
    patch({ components });
  }

  function patchActions(actions: MessageComponentsV2['actions']) {
    patch({ actions });
  }

  function addBlock(type: BlockType) {
    patchComponents([...draft.components, defaultBlock(type)]);
  }

  function updateBlock(index: number, node: ComponentV2Node) {
    patchComponents(draft.components.map((entry, i) => (i === index ? node : entry)));
  }

  function removeBlock(index: number) {
    patchComponents(draft.components.filter((_, i) => i !== index));
  }

  function moveBlock(index: number, direction: -1 | 1) {
    const next = [...draft.components];
    const target = index + direction;
    if (target < 0 || target >= next.length) {
      return;
    }
    const current = next[index];
    const swap = next[target];
    if (!current || !swap) {
      return;
    }
    next[index] = swap;
    next[target] = current;
    patchComponents(next);
  }

  const referencedActionIds = useMemo(() => {
    const ids = new Set<string>();
    const walk = (nodes: ComponentV2Node[]) => {
      for (const node of nodes) {
        if (node.type === 'button' && node.actionId) {
          ids.add(node.actionId);
        } else if (node.type === 'string_select') {
          if (node.actionId) {
            ids.add(node.actionId);
          }
          for (const option of node.options) {
            if (option.actionId) {
              ids.add(option.actionId);
            }
          }
        } else if (
          (node.type === 'user_select' ||
            node.type === 'role_select' ||
            node.type === 'channel_select' ||
            node.type === 'mentionable_select') &&
          node.actionId
        ) {
          ids.add(node.actionId);
        } else if (node.type === 'action_row') {
          walk(node.components as ComponentV2Node[]);
        } else if (node.type === 'section' && node.accessory.type === 'button' && node.accessory.actionId) {
          ids.add(node.accessory.actionId);
        } else if (node.type === 'container') {
          walk(node.components);
        }
      }
    };
    walk(draft.components);
    return [...ids].sort();
  }, [draft.components]);

  return (
    <div className={cn('grid gap-4 lg:grid-cols-2', className)}>
      <div className="space-y-4">
        <div className="flex flex-wrap items-center gap-2">
          <Label>{t('componentsV2Builder.blocks')}</Label>
          <Select onValueChange={(type) => addBlock(type as BlockType)}>
            <SelectTrigger className="h-8 w-auto min-w-[10rem]">
              <SelectValue placeholder={t('componentsV2Builder.addBlock')} />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="container">{t('componentsV2Builder.blockTypes.container')}</SelectItem>
              <SelectItem value="text_display">{t('componentsV2Builder.blockTypes.text_display')}</SelectItem>
              <SelectItem value="separator">{t('componentsV2Builder.blockTypes.separator')}</SelectItem>
              <SelectItem value="section">{t('componentsV2Builder.blockTypes.section')}</SelectItem>
              <SelectItem value="media_gallery">{t('componentsV2Builder.blockTypes.media_gallery')}</SelectItem>
              <SelectItem value="action_row">{t('componentsV2Builder.blockTypes.action_row')}</SelectItem>
            </SelectContent>
          </Select>
        </div>

        {draft.components.length === 0 ? (
          <p className="text-sm text-muted-foreground">{t('componentsV2Builder.empty')}</p>
        ) : (
          <div className="space-y-3">
            {draft.components.map((node, index) => (
              <BlockEditor
                key={`${node.type}-${index}`}
                guildId={guildId}
                node={node}
                depth={0}
                onChange={(next) => updateBlock(index, next)}
                onRemove={() => removeBlock(index)}
                onMoveUp={() => moveBlock(index, -1)}
                onMoveDown={() => moveBlock(index, 1)}
                canMoveUp={index > 0}
                canMoveDown={index < draft.components.length - 1}
                actions={draft.actions}
                onActionsChange={patchActions}
                t={t}
              />
            ))}
          </div>
        )}

        {referencedActionIds.length > 0 ? (
          <div className="space-y-3 rounded-md border border-border p-3">
            <p className="text-xs font-medium text-foreground">{t('componentsV2Builder.actionsTitle')}</p>
            {referencedActionIds.map((actionId) => (
              <ActionEditor
                key={actionId}
                guildId={guildId}
                actionId={actionId}
                action={draft.actions[actionId] ?? { type: 'NONE' }}
                onChange={(next) => patchActions({ ...draft.actions, [actionId]: next })}
                t={t}
              />
            ))}
          </div>
        ) : null}

        {showClearHint ? (
          <p className="text-xs text-muted-foreground">{t('componentsV2Builder.clearHint')}</p>
        ) : null}
        <PlaceholderHelp preset={placeholderPreset} />
        <button
          type="button"
          className="text-xs text-muted-foreground underline-offset-2 hover:underline"
          onClick={() => onChange(null)}
        >
          {t('componentsV2Builder.clear')}
        </button>
      </div>

      <div className="space-y-2">
        <Label>{t('componentsV2Builder.preview')}</Label>
        <ComponentsV2Preview payload={draft} vars={vars} t={t} />
      </div>
    </div>
  );
}

interface BlockEditorProps {
  node: ComponentV2Node;
  depth: number;
  guildId?: string;
  onChange: (next: ComponentV2Node) => void;
  onRemove?: () => void;
  onMoveUp?: () => void;
  onMoveDown?: () => void;
  canMoveUp?: boolean;
  canMoveDown?: boolean;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}

function BlockEditor({
  node,
  depth,
  guildId,
  onChange,
  onRemove,
  onMoveUp,
  onMoveDown,
  canMoveUp,
  canMoveDown,
  actions,
  onActionsChange,
  t
}: BlockEditorProps) {
  const header = (
    <div className="flex flex-wrap items-center justify-between gap-2">
      <p className="text-xs font-medium text-foreground">
        {t(`componentsV2Builder.blockTypes.${node.type}`)}
      </p>
      <div className="flex items-center gap-1">
        {onMoveUp ? (
          <Button type="button" variant="ghost" size="icon" className="size-7" disabled={!canMoveUp} onClick={onMoveUp}>
            <ChevronUp className="size-4" />
          </Button>
        ) : null}
        {onMoveDown ? (
          <Button
            type="button"
            variant="ghost"
            size="icon"
            className="size-7"
            disabled={!canMoveDown}
            onClick={onMoveDown}
          >
            <ChevronDown className="size-4" />
          </Button>
        ) : null}
        {onRemove ? (
          <Button type="button" variant="ghost" size="icon" className="size-7" onClick={onRemove}>
            <Trash2 className="size-4" />
          </Button>
        ) : null}
      </div>
    </div>
  );

  return (
    <div className={cn('space-y-3 rounded-md border border-border p-3', depth > 0 && 'ml-3 border-dashed')}>
      {header}
      <BlockFields
        node={node}
        depth={depth}
        guildId={guildId}
        onChange={onChange}
        actions={actions}
        onActionsChange={onActionsChange}
        t={t}
      />
    </div>
  );
}

function BlockFields({
  node,
  depth,
  guildId,
  onChange,
  actions,
  onActionsChange,
  t
}: Omit<BlockEditorProps, 'onRemove' | 'onMoveUp' | 'onMoveDown' | 'canMoveUp' | 'canMoveDown'>) {
  switch (node.type) {
    case 'text_display':
      return (
        <Textarea
          rows={3}
          maxLength={4000}
          value={node.content}
          placeholder={t('componentsV2Builder.textPlaceholder')}
          onChange={(event) => onChange({ ...node, content: event.target.value })}
        />
      );
    case 'separator':
      return (
        <div className="grid gap-3 sm:grid-cols-2">
          <div className="flex items-center justify-between rounded-md border border-border px-3 py-2">
            <Label>{t('componentsV2Builder.divider')}</Label>
            <Switch
              checked={node.divider !== false}
              onCheckedChange={(divider) => onChange({ ...node, divider })}
            />
          </div>
          <div className="space-y-2">
            <Label>{t('componentsV2Builder.spacing')}</Label>
            <Select
              value={node.spacing ?? 'Small'}
              onValueChange={(spacing) => onChange({ ...node, spacing: spacing as 'Small' | 'Large' })}
            >
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="Small">{t('componentsV2Builder.spacingSmall')}</SelectItem>
                <SelectItem value="Large">{t('componentsV2Builder.spacingLarge')}</SelectItem>
              </SelectContent>
            </Select>
          </div>
        </div>
      );
    case 'container':
      return (
        <div className="space-y-3">
          <div className="grid gap-3 sm:grid-cols-2">
            <div className="space-y-2">
              <Label>{t('componentsV2Builder.accentColor')}</Label>
              <Input
                type="number"
                min={0}
                max={16777215}
                value={node.accentColor ?? ''}
                placeholder={t('common.optional')}
                onChange={(event) => {
                  const parsed = Number.parseInt(event.target.value, 10);
                  onChange({
                    ...node,
                    accentColor: Number.isFinite(parsed) ? parsed : undefined
                  });
                }}
              />
            </div>
            <div className="flex items-center justify-between rounded-md border border-border px-3 py-2">
              <Label>{t('componentsV2Builder.spoiler')}</Label>
              <Switch checked={Boolean(node.spoiler)} onCheckedChange={(spoiler) => onChange({ ...node, spoiler })} />
            </div>
          </div>
          <NestedBlocksEditor
            components={node.components}
            depth={depth + 1}
            guildId={guildId}
            onChange={(components) => onChange({ ...node, components })}
            actions={actions}
            onActionsChange={onActionsChange}
            t={t}
          />
        </div>
      );
    case 'section':
      return (
        <div className="space-y-3">
          <div className="space-y-2">
            <Label>{t('componentsV2Builder.sectionText')}</Label>
            <Textarea
              rows={3}
              maxLength={4000}
              value={Array.isArray(node.text) ? node.text.join('\n---\n') : node.text}
              onChange={(event) => onChange({ ...node, text: event.target.value })}
            />
          </div>
          <div className="space-y-2">
            <Label>{t('componentsV2Builder.accessoryType')}</Label>
            <Select
              value={node.accessory.type}
              onValueChange={(type) => {
                if (type === 'thumbnail') {
                  onChange({
                    ...node,
                    accessory: { type: 'thumbnail', url: 'https://cdn.discordapp.com/embed/avatars/0.png' }
                  });
                } else {
                  onChange({
                    ...node,
                    accessory: { type: 'button', style: 'Secondary', label: 'Button' }
                  });
                }
              }}
            >
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="thumbnail">{t('componentsV2Builder.accessoryThumbnail')}</SelectItem>
                <SelectItem value="button">{t('componentsV2Builder.accessoryButton')}</SelectItem>
              </SelectContent>
            </Select>
          </div>
          {node.accessory.type === 'thumbnail' ? (
            <Input
              value={node.accessory.url}
              placeholder={t('componentsV2Builder.urlPlaceholder')}
              onChange={(event) =>
                onChange({
                  ...node,
                  accessory: { ...node.accessory, url: event.target.value }
                })
              }
            />
          ) : (
            <ButtonEditor
              guildId={guildId}
              button={node.accessory}
              onChange={(accessory) => onChange({ ...node, accessory })}
              actions={actions}
              onActionsChange={onActionsChange}
              t={t}
            />
          )}
        </div>
      );
    case 'media_gallery':
      return (
        <div className="space-y-2">
          {node.items.map((item, index) => (
            <div key={index} className="flex gap-2">
              <Input
                className="flex-1"
                value={item.url}
                placeholder={t('componentsV2Builder.urlPlaceholder')}
                onChange={(event) => {
                  const items = node.items.map((entry, i) =>
                    i === index ? { ...entry, url: event.target.value } : entry
                  );
                  onChange({ ...node, items });
                }}
              />
              <Button
                type="button"
                variant="ghost"
                size="icon"
                onClick={() => onChange({ ...node, items: node.items.filter((_, i) => i !== index) })}
              >
                <Trash2 className="size-4" />
              </Button>
            </div>
          ))}
          {node.items.length < 10 ? (
            <Button
              type="button"
              variant="outline"
              size="sm"
              onClick={() =>
                onChange({
                  ...node,
                  items: [...node.items, { url: 'https://cdn.discordapp.com/embed/avatars/0.png' }]
                })
              }
            >
              <Plus className="size-4" />
              {t('componentsV2Builder.addMediaItem')}
            </Button>
          ) : null}
        </div>
      );
    case 'action_row':
      return (
        <ActionRowEditor
          row={node}
          guildId={guildId}
          onChange={onChange}
          actions={actions}
          onActionsChange={onActionsChange}
          t={t}
        />
      );
    default:
      return null;
  }
}

function NestedBlocksEditor({
  components,
  depth,
  guildId,
  onChange,
  actions,
  onActionsChange,
  t
}: {
  components: ComponentV2Node[];
  depth: number;
  guildId?: string;
  onChange: (next: ComponentV2Node[]) => void;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}) {
  function addNested(type: BlockType) {
    onChange([...components, defaultBlock(type)]);
  }

  return (
    <div className="space-y-2">
      <Select onValueChange={(type) => addNested(type as BlockType)}>
        <SelectTrigger className="h-8">
          <SelectValue placeholder={t('componentsV2Builder.addNestedBlock')} />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="text_display">{t('componentsV2Builder.blockTypes.text_display')}</SelectItem>
          <SelectItem value="separator">{t('componentsV2Builder.blockTypes.separator')}</SelectItem>
          <SelectItem value="section">{t('componentsV2Builder.blockTypes.section')}</SelectItem>
          <SelectItem value="media_gallery">{t('componentsV2Builder.blockTypes.media_gallery')}</SelectItem>
        </SelectContent>
      </Select>
      {components.map((child, index) => (
        <BlockEditor
          key={`nested-${depth}-${index}`}
          node={child}
          depth={depth}
          guildId={guildId}
          onChange={(next) => onChange(components.map((entry, i) => (i === index ? next : entry)))}
          onRemove={() => onChange(components.filter((_, i) => i !== index))}
          onMoveUp={() => {
            if (index === 0) return;
            const next = [...components];
            const current = next[index];
            const prev = next[index - 1];
            if (!current || !prev) return;
            next[index - 1] = current;
            next[index] = prev;
            onChange(next);
          }}
          onMoveDown={() => {
            if (index >= components.length - 1) return;
            const next = [...components];
            const current = next[index];
            const following = next[index + 1];
            if (!current || !following) return;
            next[index] = following;
            next[index + 1] = current;
            onChange(next);
          }}
          canMoveUp={index > 0}
          canMoveDown={index < components.length - 1}
          actions={actions}
          onActionsChange={onActionsChange}
          t={t}
        />
      ))}
    </div>
  );
}

function ActionRowEditor({
  row,
  guildId,
  onChange,
  actions,
  onActionsChange,
  t
}: {
  row: ComponentActionRow;
  guildId?: string;
  onChange: (next: ComponentActionRow) => void;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}) {
  return (
    <div className="space-y-3">
      {row.components.map((child, index) => (
        <div key={index} className="space-y-2 rounded-md border border-dashed border-border p-2">
          <div className="flex items-center justify-between gap-2">
            <p className="text-[11px] font-medium uppercase text-muted-foreground">
              {child.type === 'button'
                ? t('componentsV2Builder.blockTypes.button')
                : t(`componentsV2Builder.selectTypes.${child.type}`)}
            </p>
            <Button
              type="button"
              variant="ghost"
              size="icon"
              className="size-7"
              onClick={() =>
                onChange({ ...row, components: row.components.filter((_, i) => i !== index) as typeof row.components })
              }
            >
              <Trash2 className="size-4" />
            </Button>
          </div>
          {child.type === 'button' ? (
            <ButtonEditor
              guildId={guildId}
              button={child}
              onChange={(button) => {
                const components = row.components.map((entry, i) => (i === index ? button : entry)) as typeof row.components;
                onChange({ ...row, components });
              }}
              actions={actions}
              onActionsChange={onActionsChange}
              t={t}
            />
          ) : (
            <SelectEditor
              guildId={guildId}
              select={child}
              onChange={(select) => {
                const components = row.components.map((entry, i) => (i === index ? select : entry)) as typeof row.components;
                onChange({ ...row, components });
              }}
              actions={actions}
              onActionsChange={onActionsChange}
              t={t}
            />
          )}
        </div>
      ))}
      {row.components.length < 5 ? (
        <div className="flex flex-wrap gap-2">
          <Button
            type="button"
            variant="outline"
            size="sm"
            onClick={() =>
              onChange({
                ...row,
                components: [
                  ...row.components,
                  { type: 'button', style: 'Secondary', label: t('componentsV2Builder.defaultButtonLabel') }
                ] as typeof row.components
              })
            }
          >
            <Plus className="size-4" />
            {t('componentsV2Builder.addButton')}
          </Button>
          <Select
            onValueChange={(type) =>
              onChange({
                ...row,
                components: [...row.components, defaultSelect(type as SelectType)] as typeof row.components
              })
            }
          >
            <SelectTrigger className="h-8 w-auto min-w-[8rem]">
              <SelectValue placeholder={t('componentsV2Builder.addSelect')} />
            </SelectTrigger>
            <SelectContent>
              {SELECT_TYPES.map((type) => (
                <SelectItem key={type} value={type}>
                  {t(`componentsV2Builder.selectTypes.${type}`)}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
      ) : null}
    </div>
  );
}

function ButtonEditor({
  button,
  guildId,
  onChange,
  actions,
  onActionsChange,
  t
}: {
  button: ComponentButton;
  guildId?: string;
  onChange: (next: ComponentButton) => void;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}) {
  const isLink = button.style === 'Link';

  function assignActionId(actionId: string) {
    onChange({ ...button, actionId });
    onActionsChange(ensureAction(actions, actionId));
  }

  return (
    <div className="space-y-2">
      <div className="grid gap-2 sm:grid-cols-2">
        <Input
          value={button.label}
          maxLength={80}
          placeholder={t('componentsV2Builder.buttonLabel')}
          onChange={(event) => onChange({ ...button, label: event.target.value })}
        />
        <Select
          value={button.style}
          onValueChange={(style) =>
            onChange({
              ...button,
              style: style as ComponentButtonStyle,
              url: style === 'Link' ? button.url ?? 'https://' : undefined,
              actionId: style === 'Link' ? undefined : button.actionId ?? newActionId('btn')
            })
          }
        >
          <SelectTrigger>
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {BUTTON_STYLES.map((style) => (
              <SelectItem key={style} value={style}>
                {t(`componentsV2Builder.buttonStyles.${style}`)}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>
      {isLink ? (
        <Input
          value={button.url ?? ''}
          placeholder={t('componentsV2Builder.linkUrl')}
          onChange={(event) => onChange({ ...button, url: event.target.value })}
        />
      ) : (
        <div className="grid gap-2 sm:grid-cols-2">
          <Input
            value={button.actionId ?? ''}
            placeholder={t('componentsV2Builder.actionId')}
            onChange={(event) => assignActionId(event.target.value.trim() || newActionId('btn'))}
          />
          <Select
            value={button.actionId ? actions[button.actionId]?.type ?? 'NONE' : 'NONE'}
            onValueChange={(type) => {
              const actionId = button.actionId ?? newActionId('btn');
              assignActionId(actionId);
              onActionsChange(
                ensureAction(actions, actionId, defaultAction(type as ComponentActionType))
              );
            }}
          >
            <SelectTrigger>
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              {ACTION_TYPES.filter((type) => type !== 'ASSIGN_SELECTED_ROLES').map((type) => (
                <SelectItem key={type} value={type}>
                  {t(`componentsV2Builder.actionTypes.${type}`)}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
      )}
      {!isLink && button.actionId && guildId ? (
        <ActionEditor
          guildId={guildId}
          actionId={button.actionId}
          action={actions[button.actionId] ?? { type: 'NONE' }}
          onChange={(next) => onActionsChange({ ...actions, [button.actionId!]: next })}
          compact
          t={t}
        />
      ) : null}
    </div>
  );
}

function SelectEditor({
  select,
  guildId,
  onChange,
  actions,
  onActionsChange,
  t
}: {
  select: ComponentSelect;
  guildId?: string;
  onChange: (next: ComponentSelect) => void;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}) {
  if (select.type === 'string_select') {
    return (
      <StringSelectEditor
        select={select}
        onChange={onChange}
        actions={actions}
        onActionsChange={onActionsChange}
        t={t}
      />
    );
  }

  const actionId = select.actionId ?? '';

  function patchSelect(next: ComponentSelect) {
    onChange(next);
    if ('actionId' in next && next.actionId) {
      const defaultType = next.type === 'role_select' ? 'ASSIGN_SELECTED_ROLES' : 'NONE';
      onActionsChange(ensureAction(actions, next.actionId, defaultAction(defaultType)));
    }
  }

  return (
    <div className="space-y-2">
      <Input
        value={select.placeholder ?? ''}
        placeholder={t('componentsV2Builder.selectPlaceholder')}
        onChange={(event) =>
          patchSelect({
            ...select,
            actionId: select.actionId ?? newActionId(select.type),
            placeholder: event.target.value || undefined
          })
        }
      />
      <Input
        value={actionId}
        placeholder={t('componentsV2Builder.actionId')}
        onChange={(event) =>
          patchSelect({ ...select, actionId: event.target.value.trim() || newActionId(select.type) })
        }
        onFocus={() => {
          if (!select.actionId?.trim()) {
            patchSelect({ ...select, actionId: newActionId(select.type) });
          }
        }}
      />
      {guildId && select.actionId ? (
        <ActionEditor
          guildId={guildId}
          actionId={select.actionId}
          action={
            actions[select.actionId] ??
            defaultAction(select.type === 'role_select' ? 'ASSIGN_SELECTED_ROLES' : 'NONE')
          }
          onChange={(next) => onActionsChange({ ...actions, [select.actionId!]: next })}
          compact
          t={t}
        />
      ) : null}
    </div>
  );
}

function StringSelectEditor({
  select,
  onChange,
  actions,
  onActionsChange,
  t
}: {
  select: ComponentStringSelect;
  onChange: (next: ComponentStringSelect) => void;
  actions: MessageComponentsV2['actions'];
  onActionsChange: (next: MessageComponentsV2['actions']) => void;
  t: (key: string) => string;
}) {
  function updateOption(index: number, patch: Partial<StringSelectOption>) {
    onChange({
      ...select,
      options: select.options.map((option, i) => (i === index ? { ...option, ...patch } : option))
    });
  }

  return (
    <div className="space-y-2">
      <Input
        value={select.placeholder ?? ''}
        placeholder={t('componentsV2Builder.selectPlaceholder')}
        onChange={(event) => onChange({ ...select, placeholder: event.target.value || undefined })}
      />
      <Input
        value={select.actionId ?? ''}
        placeholder={t('componentsV2Builder.centralActionId')}
        onChange={(event) => {
          const actionId = event.target.value.trim() || undefined;
          onChange({ ...select, actionId });
          if (actionId) {
            onActionsChange(ensureAction(actions, actionId));
          }
        }}
      />
      {select.options.map((option, index) => (
        <div key={index} className="grid gap-2 rounded-md border border-border p-2 sm:grid-cols-2">
          <Input
            value={option.label}
            placeholder={t('componentsV2Builder.optionLabel')}
            onChange={(event) => updateOption(index, { label: event.target.value })}
          />
          <Input
            value={option.value}
            placeholder={t('componentsV2Builder.optionValue')}
            onChange={(event) => updateOption(index, { value: event.target.value })}
          />
          <Input
            className="sm:col-span-2"
            value={option.actionId ?? ''}
            placeholder={t('componentsV2Builder.optionActionId')}
            onChange={(event) => {
              const actionId = event.target.value.trim() || undefined;
              updateOption(index, { actionId });
              if (actionId) {
                onActionsChange(ensureAction(actions, actionId));
              }
            }}
          />
          <Button
            type="button"
            variant="ghost"
            size="sm"
            className="sm:col-span-2"
            onClick={() => onChange({ ...select, options: select.options.filter((_, i) => i !== index) })}
          >
            <Trash2 className="size-4" />
            {t('componentsV2Builder.removeOption')}
          </Button>
        </div>
      ))}
      {select.options.length < 25 ? (
        <Button
          type="button"
          variant="outline"
          size="sm"
          onClick={() =>
            onChange({
              ...select,
              options: [...select.options, { label: `Option ${select.options.length + 1}`, value: `opt${select.options.length + 1}` }]
            })
          }
        >
          <Plus className="size-4" />
          {t('componentsV2Builder.addOption')}
        </Button>
      ) : null}
    </div>
  );
}

function ActionEditor({
  actionId,
  action,
  onChange,
  guildId,
  compact = false,
  t
}: {
  actionId: string;
  action: ComponentAction;
  onChange: (next: ComponentAction) => void;
  guildId?: string;
  compact?: boolean;
  t: (key: string) => string;
}) {
  const needsContent = action.type === 'EPHEMERAL_REPLY' || action.type === 'PUBLIC_REPLY';
  const needsRole =
    action.type === 'ASSIGN_ROLE' || action.type === 'REMOVE_ROLE' || action.type === 'TOGGLE_ROLE';

  return (
    <div className={cn('space-y-2 rounded-md border border-border p-2', compact && 'border-dashed')}>
      {!compact ? (
        <p className="text-[11px] font-mono text-muted-foreground">{actionId}</p>
      ) : null}
      <Select
        value={action.type}
        onValueChange={(type) => onChange(defaultAction(type as ComponentActionType))}
      >
        <SelectTrigger>
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          {ACTION_TYPES.map((type) => (
            <SelectItem key={type} value={type}>
              {t(`componentsV2Builder.actionTypes.${type}`)}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
      {needsContent ? (
        <Textarea
          rows={2}
          maxLength={2000}
          value={action.content ?? ''}
          placeholder={t('componentsV2Builder.replyContent')}
          onChange={(event) => onChange({ ...action, content: event.target.value })}
        />
      ) : null}
      {needsRole && guildId ? (
        <DiscordRoleSelect
          guildId={guildId}
          value={action.roleId ?? ''}
          onChange={(roleId) => onChange({ ...action, roleId: roleId || undefined })}
          placeholder={t('componentsV2Builder.roleAction')}
        />
      ) : null}
    </div>
  );
}

function buttonStyleClass(style: ComponentButtonStyle): string {
  switch (style) {
    case 'Primary':
      return 'bg-[#5865F2] text-white';
    case 'Success':
      return 'bg-[#248046] text-white';
    case 'Danger':
      return 'bg-[#DA373C] text-white';
    case 'Link':
      return 'bg-transparent text-[#00a8fc] underline';
    default:
      return 'bg-[#4E5058] text-white';
  }
}

function ComponentsV2Preview({
  payload,
  vars,
  t
}: {
  payload: MessageComponentsV2;
  vars: Record<string, string>;
  t: (key: string) => string;
}) {
  return (
    <div className="rounded-md border border-[#1e1f22] bg-[#313338] p-3">
      <div className="space-y-2">
        {payload.components.map((node, index) => (
          <PreviewNode key={index} node={node} vars={vars} t={t} />
        ))}
      </div>
    </div>
  );
}

function PreviewNode({
  node,
  vars,
  t
}: {
  node: ComponentV2Node;
  vars: Record<string, string>;
  t: (key: string) => string;
}) {
  switch (node.type) {
    case 'text_display':
      return (
        <p className="whitespace-pre-wrap text-sm leading-5 text-[#dbdee1]">
          {applyPreviewVars(node.content, vars) || t('componentsV2Builder.previewEmptyText')}
        </p>
      );
    case 'separator':
      return (
        <div
          className={cn(
            'py-1',
            node.divider !== false && 'border-t border-[#3f4147]',
            node.spacing === 'Large' && 'py-3'
          )}
        />
      );
    case 'container': {
      const accent =
        typeof node.accentColor === 'number'
          ? `#${Math.max(0, Math.min(0xffffff, node.accentColor)).toString(16).padStart(6, '0')}`
          : undefined;
      return (
        <div
          className="space-y-2 rounded-[4px] bg-[#2b2d31] p-3"
          style={accent ? { borderLeft: `4px solid ${accent}` } : undefined}
        >
          {node.components.map((child, index) => (
            <PreviewNode key={index} node={child} vars={vars} t={t} />
          ))}
        </div>
      );
    }
    case 'section': {
      const text = Array.isArray(node.text)
        ? node.text.map((part) => applyPreviewVars(part, vars)).join('\n')
        : applyPreviewVars(node.text, vars);
      const thumb =
        node.accessory.type === 'thumbnail' ? previewUrl(node.accessory.url, vars) : undefined;
      return (
        <div className="flex gap-3">
          <p className="min-w-0 flex-1 whitespace-pre-wrap text-sm text-[#dbdee1]">{text}</p>
          {node.accessory.type === 'button' ? (
            <span
              className={cn(
                'inline-flex h-8 shrink-0 items-center rounded px-3 text-xs font-medium',
                buttonStyleClass(node.accessory.style)
              )}
            >
              {applyPreviewVars(node.accessory.label, vars)}
            </span>
          ) : thumb ? (
            // eslint-disable-next-line @next/next/no-img-element
            <img src={thumb} alt="" className="size-16 shrink-0 rounded object-cover" />
          ) : null}
        </div>
      );
    }
    case 'media_gallery':
      return (
        <div className="grid grid-cols-2 gap-2">
          {node.items.map((item, index) => {
            const url = previewUrl(item.url, vars);
            return url ? (
              // eslint-disable-next-line @next/next/no-img-element
              <img key={index} src={url} alt="" className="aspect-video w-full rounded object-cover" />
            ) : (
              <div
                key={index}
                className="flex aspect-video items-center justify-center rounded bg-[#2b2d31] text-xs text-[#949ba4]"
              >
                {t('componentsV2Builder.previewInvalidUrl')}
              </div>
            );
          })}
        </div>
      );
    case 'action_row':
      return (
        <div className="flex flex-wrap gap-2">
          {node.components.map((child, index) =>
            child.type === 'button' ? (
              <span
                key={index}
                className={cn(
                  'inline-flex h-8 items-center rounded px-3 text-xs font-medium',
                  buttonStyleClass(child.style)
                )}
              >
                {applyPreviewVars(child.label, vars)}
              </span>
            ) : (
              <span
                key={index}
                className="inline-flex h-8 min-w-[10rem] items-center rounded bg-[#4E5058] px-3 text-xs text-[#dbdee1]"
              >
                {child.placeholder ?? t(`componentsV2Builder.selectTypes.${child.type}`)}
              </span>
            )
          )}
        </div>
      );
    default:
      return null;
  }
}
