/**
 * Element Defaults
 *
 * Single source of truth for UI element default values, creation, and merging.
 * Used by constructor, runtime, element-type-defaults, and project-element-defaults pages.
 */

import type {
  CanvasElement,
  CanvasElementType,
  GalleryCard,
  CarouselSlide,
  NavigationButtonKind,
} from '../types/constructor';
import { ELEMENT_STYLE_PROPS } from './elementStyles';

/**
 * Generate a local unique ID for elements
 */
export const createLocalId = (): string => {
  if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
    return window.crypto.randomUUID();
  }
  return `element_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
};

/**
 * Clamp a number between min and max
 */
export const clamp = (value: number, min: number, max: number): number =>
  Math.min(Math.max(value, min), max);

/**
 * Normalize appearDelaySec value
 */
export const normalizeAppearDelaySec = (value: unknown): number => {
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed < 0) return 0;
  return parsed;
};

/**
 * Normalize appearDurationSec value (null means infinite)
 */
export const normalizeAppearDurationSec = (value: unknown): number | null => {
  if (value === null || value === undefined || value === '') return null;
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0) return null;
  return parsed;
};

/**
 * Labels for each element type
 */
export const ELEMENT_TYPE_LABELS: Record<CanvasElementType, string> = {
  navigation_next: 'Navigation: Forward',
  navigation_prev: 'Navigation: Back',
  spot: 'Hotspot',
  description: 'Description',
  tooltip: 'Tooltip',
  gallery: 'Gallery',
  carousel: 'Carousel',
  logo: 'Logo',
  video_player: 'Video Player',
  audio_player: 'Audio Player',
  popup: 'Popup',
};

/**
 * Get navigation button label based on type
 */
export const getNavigationButtonLabel = (
  type: 'navigation_next' | 'navigation_prev',
): string => (type === 'navigation_next' ? 'Forward' : 'Back');

/**
 * Get navigation button kind based on type
 */
export const getNavigationButtonKind = (
  type: 'navigation_next' | 'navigation_prev',
): NavigationButtonKind => (type === 'navigation_prev' ? 'back' : 'forward');

/**
 * Get navigation type from kind
 */
export const getNavigationTypeFromKind = (
  kind: NavigationButtonKind,
): 'navigation_next' | 'navigation_prev' =>
  kind === 'back' ? 'navigation_prev' : 'navigation_next';

/**
 * Type-specific default values for elements.
 * These are the base defaults used when creating new elements.
 */
export const TYPE_SPECIFIC_DEFAULTS: Partial<
  Record<CanvasElementType, Partial<CanvasElement>>
> = {
  navigation_next: {
    navLabel: 'Forward',
    navType: 'forward',
    navDisabled: false,
    iconUrl: '',
    transitionReverseMode: 'auto_reverse',
  },
  navigation_prev: {
    navLabel: 'Back',
    navType: 'back',
    navDisabled: false,
    iconUrl: '',
    transitionReverseMode: 'auto_reverse',
  },
  tooltip: {
    iconUrl: '',
    tooltipTitle: 'Tooltip title',
    tooltipText: 'Tooltip text',
  },
  description: {
    iconUrl: '',
    descriptionTitle: 'TITLE',
    descriptionText: '',
    descriptionTitleFontSize: '24px',
    descriptionTextFontSize: '16px',
    descriptionTitleFontFamily: 'inherit',
    descriptionTextFontFamily: 'inherit',
    descriptionTitleColor: '#ffffff',
    descriptionTextColor: '#ffffff',
  },
  gallery: {
    galleryCards: [],
  },
  carousel: {
    carouselSlides: [],
    carouselPrevIconUrl: '',
    carouselNextIconUrl: '',
  },
  video_player: {
    mediaUrl: '',
    mediaAutoplay: true,
    mediaLoop: true,
    mediaMuted: true,
  },
  audio_player: {
    mediaUrl: '',
    mediaAutoplay: true,
    mediaLoop: true,
    mediaMuted: false,
  },
  logo: {
    iconUrl: '',
  },
  spot: {
    iconUrl: '',
  },
  popup: {},
};

/**
 * Create a default gallery card
 */
export const createDefaultGalleryCard = (index: number): GalleryCard => ({
  id: createLocalId(),
  imageUrl: '',
  title: `Card ${index + 1}`,
  description: '',
});

/**
 * Create a default carousel slide
 */
export const createDefaultCarouselSlide = (index: number): CarouselSlide => ({
  id: createLocalId(),
  imageUrl: '',
  caption: `Slide ${index + 1}`,
});

/**
 * Create a new element with default values.
 * The index is used to offset position for multiple elements.
 */
export const createDefaultElement = (
  type: CanvasElementType,
  index = 0,
): CanvasElement => {
  const base: CanvasElement = {
    id: createLocalId(),
    type,
    label: ELEMENT_TYPE_LABELS[type] || type,
    xPercent: clamp(12 + index * 4, 5, 80),
    yPercent: clamp(16 + index * 6, 8, 85),
    appearDelaySec: 0,
    appearDurationSec: null,
  };

  const typeDefaults = TYPE_SPECIFIC_DEFAULTS[type] || {};

  // Handle gallery with initial card
  if (isGalleryElementType(type)) {
    return {
      ...base,
      ...typeDefaults,
      galleryCards: [createDefaultGalleryCard(0)],
    };
  }

  // Handle carousel with initial slide
  if (isCarouselElementType(type)) {
    return {
      ...base,
      ...typeDefaults,
      carouselSlides: [createDefaultCarouselSlide(0)],
    };
  }

  return {
    ...base,
    ...typeDefaults,
  };
};

/**
 * Normalize a gallery card from unknown input
 */
export const normalizeGalleryCard = (
  card: Record<string, unknown>,
  index: number,
): GalleryCard => ({
  id: String(card?.id || createLocalId()),
  imageUrl: String(card?.imageUrl || ''),
  title: String(card?.title || `Card ${index + 1}`),
  description: String(card?.description || ''),
});

/**
 * Normalize a carousel slide from unknown input
 */
export const normalizeCarouselSlide = (
  slide: Record<string, unknown>,
  index: number,
): CarouselSlide => ({
  id: String(slide?.id || createLocalId()),
  imageUrl: String(slide?.imageUrl || ''),
  caption: String(slide?.caption || `Slide ${index + 1}`),
});

/**
 * Merge an element with project/global defaults.
 * Used when loading elements from the database or creating new elements.
 *
 * @param element - The element to merge
 * @param defaults - Project or global defaults to apply
 * @param options.preferElementValues - If true, element values take precedence over defaults
 */
export const mergeElementWithDefaults = (
  element: CanvasElement,
  defaults?: Partial<CanvasElement>,
  options?: { preferElementValues?: boolean },
): CanvasElement => {
  if (!defaults) return element;

  const preferElementValues = Boolean(options?.preferElementValues);
  const base = preferElementValues ? defaults : element;
  const override = preferElementValues ? element : defaults;

  const merged: CanvasElement = {
    ...base,
    ...override,
    id: element.id,
    type: element.type,
    label: element.label || defaults.label || element.type,
    xPercent: element.xPercent ?? defaults.xPercent ?? 50,
    yPercent: element.yPercent ?? defaults.yPercent ?? 50,
  };

  // For style properties, use defaults if element has empty/null/undefined value
  // This ensures DB defaults are applied when element has no explicit value
  const elementRecord = element as unknown as Record<string, unknown>;
  const defaultsRecord = defaults as unknown as Record<string, unknown>;
  const mergedRecord = merged as unknown as Record<string, unknown>;

  ELEMENT_STYLE_PROPS.forEach((prop) => {
    const elementValue = elementRecord[prop];
    const defaultValue = defaultsRecord[prop];
    const elementIsEmpty =
      elementValue === '' ||
      elementValue === undefined ||
      elementValue === null;
    const defaultHasValue =
      defaultValue !== undefined &&
      defaultValue !== null &&
      defaultValue !== '';
    if (elementIsEmpty && defaultHasValue) {
      mergedRecord[prop] = defaultValue;
    }
  });

  // Normalize position values
  merged.xPercent = clamp(Number(merged.xPercent ?? element.xPercent), 0, 100);
  merged.yPercent = clamp(Number(merged.yPercent ?? element.yPercent), 0, 100);
  merged.appearDelaySec = normalizeAppearDelaySec(merged.appearDelaySec);
  merged.appearDurationSec = normalizeAppearDurationSec(
    merged.appearDurationSec,
  );

  // Handle gallery cards array
  if (isGalleryElementType(merged.type)) {
    const cards = preferElementValues
      ? Array.isArray(element.galleryCards)
        ? element.galleryCards
        : defaults.galleryCards || []
      : Array.isArray(defaults.galleryCards)
        ? defaults.galleryCards
        : element.galleryCards || [];
    merged.galleryCards = cards.map((card, i) =>
      normalizeGalleryCard(card as unknown as Record<string, unknown>, i),
    );
  }

  // Handle carousel slides array
  if (isCarouselElementType(merged.type)) {
    const slides = preferElementValues
      ? Array.isArray(element.carouselSlides)
        ? element.carouselSlides
        : defaults.carouselSlides || []
      : Array.isArray(defaults.carouselSlides)
        ? defaults.carouselSlides
        : element.carouselSlides || [];
    merged.carouselSlides = slides.map((slide, i) =>
      normalizeCarouselSlide(slide as unknown as Record<string, unknown>, i),
    );
  }

  return merged;
};

/**
 * Parse element settings from JSON string or object.
 * Used when loading element defaults from the database.
 */
export const parseElementSettings = (
  settingsValue?: string | Record<string, unknown>,
): Partial<CanvasElement> => {
  if (!settingsValue) return {};

  let settings: Record<string, unknown> = {};

  if (typeof settingsValue === 'string') {
    try {
      settings = JSON.parse(settingsValue);
    } catch {
      return {};
    }
  } else {
    settings = settingsValue;
  }

  // Parse gallery cards if present
  if (Array.isArray(settings.galleryCards)) {
    settings.galleryCards = settings.galleryCards.map((card, i) =>
      normalizeGalleryCard(card as Record<string, unknown>, i),
    );
  }

  // Parse carousel slides if present
  if (Array.isArray(settings.carouselSlides)) {
    settings.carouselSlides = settings.carouselSlides.map((slide, i) =>
      normalizeCarouselSlide(slide as Record<string, unknown>, i),
    );
  }

  return settings as Partial<CanvasElement>;
};

/**
 * Check if a value is empty (null, undefined, or empty string)
 */
const isEmpty = (value: unknown): boolean =>
  value === null || value === undefined || value === '';

/**
 * Add value to settings if not empty
 */
const addIfNotEmpty = (
  settings: Record<string, unknown>,
  key: string,
  value: unknown,
): void => {
  if (!isEmpty(value)) {
    settings[key] = value;
  }
};

/**
 * Build settings JSON object from element properties.
 * Used when saving element defaults to the database.
 */
export const buildElementSettings = (
  element: Partial<CanvasElement>,
  elementType: CanvasElementType | string,
): Record<string, unknown> => {
  const settings: Record<string, unknown> = {};

  // Common properties
  addIfNotEmpty(settings, 'label', element.label);
  if (element.xPercent !== undefined) settings.xPercent = element.xPercent;
  if (element.yPercent !== undefined) settings.yPercent = element.yPercent;
  if (element.appearDelaySec !== undefined) {
    settings.appearDelaySec = normalizeAppearDelaySec(element.appearDelaySec);
  }
  if (element.appearDurationSec !== undefined) {
    settings.appearDurationSec = normalizeAppearDurationSec(
      element.appearDurationSec,
    );
  }

  // Style properties
  ELEMENT_STYLE_PROPS.forEach((prop) => {
    const value = (element as Record<string, unknown>)[prop];
    addIfNotEmpty(settings, prop, value);
  });

  // Effect properties
  const effectProps = [
    'appearAnimation',
    'appearAnimationDuration',
    'appearAnimationEasing',
    'hoverScale',
    'hoverOpacity',
    'hoverBackgroundColor',
    'hoverColor',
    'hoverBoxShadow',
    'hoverTransitionDuration',
    'focusScale',
    'focusOpacity',
    'focusOutline',
    'focusBoxShadow',
    'activeScale',
    'activeOpacity',
    'activeBackgroundColor',
  ];
  effectProps.forEach((prop) => {
    const value = (element as Record<string, unknown>)[prop];
    addIfNotEmpty(settings, prop, value);
  });

  // Navigation type settings
  if (isNavigationElementType(elementType)) {
    addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
    addIfNotEmpty(settings, 'navLabel', element.navLabel);
    addIfNotEmpty(settings, 'navType', element.navType);
    if (element.navDisabled !== undefined) {
      settings.navDisabled = element.navDisabled;
    }
    addIfNotEmpty(settings, 'targetPageId', element.targetPageId);
    addIfNotEmpty(settings, 'targetPageSlug', element.targetPageSlug);
    addIfNotEmpty(settings, 'transitionVideoUrl', element.transitionVideoUrl);
    addIfNotEmpty(
      settings,
      'transitionReverseMode',
      element.transitionReverseMode,
    );
    addIfNotEmpty(settings, 'reverseVideoUrl', element.reverseVideoUrl);
  }

  // Tooltip type settings
  if (isTooltipElementType(elementType)) {
    addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
    addIfNotEmpty(settings, 'tooltipTitle', element.tooltipTitle);
    addIfNotEmpty(settings, 'tooltipText', element.tooltipText);
    addIfNotEmpty(
      settings,
      'tooltipTitleFontFamily',
      element.tooltipTitleFontFamily,
    );
    addIfNotEmpty(
      settings,
      'tooltipTextFontFamily',
      element.tooltipTextFontFamily,
    );
  }

  // Description type settings
  if (isDescriptionElementType(elementType)) {
    addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
    addIfNotEmpty(settings, 'descriptionTitle', element.descriptionTitle);
    addIfNotEmpty(settings, 'descriptionText', element.descriptionText);
    addIfNotEmpty(
      settings,
      'descriptionTitleFontSize',
      element.descriptionTitleFontSize,
    );
    addIfNotEmpty(
      settings,
      'descriptionTextFontSize',
      element.descriptionTextFontSize,
    );
    addIfNotEmpty(
      settings,
      'descriptionTitleFontFamily',
      element.descriptionTitleFontFamily,
    );
    addIfNotEmpty(
      settings,
      'descriptionTextFontFamily',
      element.descriptionTextFontFamily,
    );
    addIfNotEmpty(
      settings,
      'descriptionTitleColor',
      element.descriptionTitleColor,
    );
    addIfNotEmpty(
      settings,
      'descriptionTextColor',
      element.descriptionTextColor,
    );
  }

  // Gallery type settings
  if (isGalleryElementType(elementType)) {
    if (Array.isArray(element.galleryCards)) {
      settings.galleryCards = element.galleryCards.map((card, i) => ({
        id: String(card.id || createLocalId()),
        imageUrl: card.imageUrl || '',
        title: card.title || `Card ${i + 1}`,
        description: card.description || '',
      }));
    }
    addIfNotEmpty(
      settings,
      'galleryTitleFontFamily',
      element.galleryTitleFontFamily,
    );
    addIfNotEmpty(
      settings,
      'galleryTextFontFamily',
      element.galleryTextFontFamily,
    );
  }

  // Carousel type settings
  if (isCarouselElementType(elementType)) {
    if (Array.isArray(element.carouselSlides)) {
      settings.carouselSlides = element.carouselSlides.map((slide, i) => ({
        id: String(slide.id || createLocalId()),
        imageUrl: slide.imageUrl || '',
        caption: slide.caption || `Slide ${i + 1}`,
      }));
    }
    addIfNotEmpty(settings, 'carouselPrevIconUrl', element.carouselPrevIconUrl);
    addIfNotEmpty(settings, 'carouselNextIconUrl', element.carouselNextIconUrl);
    addIfNotEmpty(
      settings,
      'carouselCaptionFontFamily',
      element.carouselCaptionFontFamily,
    );
  }

  // Media type settings
  if (isMediaElementType(elementType)) {
    addIfNotEmpty(settings, 'mediaUrl', element.mediaUrl);
    if (element.mediaAutoplay !== undefined) {
      settings.mediaAutoplay = element.mediaAutoplay;
    }
    if (element.mediaLoop !== undefined) {
      settings.mediaLoop = element.mediaLoop;
    }
    if (element.mediaMuted !== undefined) {
      settings.mediaMuted = element.mediaMuted;
    }
  }

  // Logo/spot type settings
  if (isLogoElementType(elementType) || isSpotElementType(elementType)) {
    addIfNotEmpty(settings, 'iconUrl', element.iconUrl);
  }

  return settings;
};

/**
 * Type detection helpers for element types.
 * Single source of truth used across the application.
 */

/**
 * Check if a type is a navigation element type
 */
export const isNavigationElementType = (
  type: string,
): type is 'navigation_next' | 'navigation_prev' =>
  type === 'navigation_next' || type === 'navigation_prev';

/**
 * Check if a type is a tooltip element type
 */
export const isTooltipElementType = (type: string): type is 'tooltip' =>
  type === 'tooltip';

/**
 * Check if a type is a description element type
 */
export const isDescriptionElementType = (type: string): type is 'description' =>
  type === 'description';

/**
 * Check if a type is a gallery element type
 */
export const isGalleryElementType = (type: string): type is 'gallery' =>
  type === 'gallery';

/**
 * Check if a type is a carousel element type
 */
export const isCarouselElementType = (type: string): type is 'carousel' =>
  type === 'carousel';

/**
 * Check if a type is a media (video/audio player) element type
 */
export const isMediaElementType = (
  type: string,
): type is 'video_player' | 'audio_player' =>
  type === 'video_player' || type === 'audio_player';

/**
 * Check if a type is a video player element type
 */
export const isVideoPlayerElementType = (
  type: string,
): type is 'video_player' => type === 'video_player';

/**
 * Check if a type is an audio player element type
 */
export const isAudioPlayerElementType = (
  type: string,
): type is 'audio_player' => type === 'audio_player';

/**
 * Check if a type is a logo element type
 */
export const isLogoElementType = (type: string): type is 'logo' =>
  type === 'logo';

/**
 * Check if a type is a spot (hotspot) element type
 */
export const isSpotElementType = (type: string): type is 'spot' =>
  type === 'spot';

/**
 * Check if a type is a popup element type
 */
export const isPopupElementType = (type: string): type is 'popup' =>
  type === 'popup';
