/**
 * Domain Value Object: MediaReference
 * 
 * Discriminated union representing different types of media references:
 * - system-default: Pre-defined default media (e.g., default avatar/logo)
 * - generated: Media generated by AI/algorithm (references generation request)
 * - uploaded: User-uploaded media (references media entity)
 * - none: Explicit no-media placeholder
 * 
 * Follows clean architecture and TDD principles.
 */

import type { IValueObject } from '@core/shared/domain';

// Variant types for system-default references
export type MediaVariant = 'avatar' | 'logo';

// Specific variants for avatars (deterministic selection)
export type AvatarVariant = 'male' | 'female' | 'neutral';

// Discriminated union types
export interface SystemDefaultRef {
  type: 'system-default';
  variant: MediaVariant;
  avatarVariant?: AvatarVariant | undefined; // Only used when variant is 'avatar'
}

export interface GeneratedRef {
  type: 'generated';
  generationRequestId: string;
}

export interface UploadedRef {
  type: 'uploaded';
  mediaId: string;
}

export interface NoneRef {
  type: 'none';
}

// Union of all reference types
export type MediaReferenceProps = SystemDefaultRef | GeneratedRef | UploadedRef | NoneRef;

// Type guards
function isSystemDefaultRef(props: unknown): props is SystemDefaultRef {
  const typedProps = props as SystemDefaultRef;
  if (typedProps?.type !== 'system-default') {
    return false;
  }
  if (typedProps.variant !== 'avatar' && typedProps.variant !== 'logo') {
    return false;
  }
  // avatarVariant is optional, but if present must be valid
  if (typedProps.avatarVariant !== undefined) {
    return typedProps.avatarVariant === 'male' || typedProps.avatarVariant === 'female' || typedProps.avatarVariant === 'neutral';
  }
  return true;
}

function isGeneratedRef(props: unknown): props is GeneratedRef {
  const typedProps = props as GeneratedRef;
  return typedProps?.type === 'generated' &&
         typeof typedProps.generationRequestId === 'string' &&
         typedProps.generationRequestId.trim().length > 0;
}

function isUploadedRef(props: unknown): props is UploadedRef {
  const typedProps = props as UploadedRef;
  return typedProps?.type === 'uploaded' &&
         typeof typedProps.mediaId === 'string' &&
         typedProps.mediaId.trim().length > 0;
}

function isNoneRef(props: unknown): props is NoneRef {
  const typedProps = props as NoneRef;
  return typedProps?.type === 'none' &&
         Object.keys(typedProps).length === 1; // Only 'type' property
}

export class MediaReference implements IValueObject<MediaReferenceProps> {
  public readonly props: MediaReferenceProps;

  private constructor(props: MediaReferenceProps) {
    this.props = props;
  }

  /**
   * Factory method to create system-default reference
   */
  static createSystemDefault(variant: MediaVariant = 'avatar', avatarVariant?: AvatarVariant): MediaReference {
    const props: SystemDefaultRef = { type: 'system-default', variant };
    if (variant === 'avatar' && avatarVariant) {
      props.avatarVariant = avatarVariant;
    }
    return new MediaReference(props);
  }

  /**
   * Factory method for system default (alias for task compatibility)
   */
  static systemDefault(variant: MediaVariant | AvatarVariant = 'avatar'): MediaReference {
    // If it's an avatar variant, use it
    if (variant === 'male' || variant === 'female' || variant === 'neutral') {
      return new MediaReference({ type: 'system-default', variant: 'avatar', avatarVariant: variant });
    }
    // Otherwise it's a regular variant
    return new MediaReference({ type: 'system-default', variant: variant as MediaVariant });
  }

  /**
   * Factory method for generated references (alias for task compatibility)
   */
  static generated(type: string, id: string): MediaReference {
    return new MediaReference({ type: 'generated', generationRequestId: `${type}-${id}` });
  }

  /**
   * Factory method to create generated reference
   */
  static createGenerated(generationRequestId: string): MediaReference {
    const trimmed = generationRequestId.trim();
    if (!trimmed) {
      throw new Error('Generation request ID is required');
    }
    return new MediaReference({ type: 'generated', generationRequestId: trimmed });
  }

  /**
   * Factory method to create uploaded reference
   */
  static createUploaded(mediaId: string): MediaReference {
    const trimmed = mediaId.trim();
    if (!trimmed) {
      throw new Error('Media ID is required');
    }
    return new MediaReference({ type: 'uploaded', mediaId: trimmed });
  }

  /**
   * Factory method to create none reference
   */
  static createNone(): MediaReference {
    return new MediaReference({ type: 'none' });
  }

  /**
   * Deserialize from JSON
   */
  static fromJSON(json: Record<string, unknown>): MediaReference {
    if (!json || typeof json !== 'object') {
      throw new Error('Invalid JSON: must be an object');
    }

    const type = json.type;
    if (!type || typeof type !== 'string') {
      throw new Error('Type is required');
    }

    switch (type) {
      case 'system-default':
        if (!isSystemDefaultRef(json)) {
          throw new Error('Invalid variant for system-default. Must be "avatar" or "logo"');
        }
        return new MediaReference({
          type: 'system-default',
          variant: json.variant,
          avatarVariant: json.avatarVariant
        });

      case 'generated':
        if (!isGeneratedRef(json)) {
          throw new Error('Generation request ID is required');
        }
        return new MediaReference({ type: 'generated', generationRequestId: json.generationRequestId });

      case 'uploaded':
        if (!isUploadedRef(json)) {
          throw new Error('Media ID is required');
        }
        return new MediaReference({ type: 'uploaded', mediaId: json.mediaId });

      case 'none':
        if (!isNoneRef(json)) {
          throw new Error('None type should not have additional properties');
        }
        return new MediaReference({ type: 'none' });

      default:
        throw new Error('Invalid type');
    }
  }

  /**
   * Serialize to JSON
   */
  toJSON(): MediaReferenceProps {
    return { ...this.props };
  }

  /**
   * Get the type of this reference
   */
  get type(): MediaReferenceProps['type'] {
    return this.props.type;
  }

  /**
   * Get variant (only for system-default)
   */
  get variant(): MediaVariant | undefined {
    return this.props.type === 'system-default' ? this.props.variant : undefined;
  }

  /**
   * Get avatar variant (only for system-default with avatar variant)
   */
  get avatarVariant(): AvatarVariant | undefined {
    return this.props.type === 'system-default' ? this.props.avatarVariant : undefined;
  }

  /**
   * Get generation request ID (only for generated)
   */
  get generationRequestId(): string | undefined {
    return this.props.type === 'generated' ? this.props.generationRequestId : undefined;
  }

  /**
   * Get media ID (only for uploaded)
   */
  get mediaId(): string | undefined {
    return this.props.type === 'uploaded' ? this.props.mediaId : undefined;
  }

  /**
   * Equality comparison
   */
  equals(other: IValueObject<MediaReferenceProps>): boolean {
    if (!(other instanceof MediaReference)) {
      return false;
    }

    const a = this.props;
    const b = other.props;

    if (a.type !== b.type) {
      return false;
    }

    switch (a.type) {
      case 'system-default':
        return b.type === 'system-default' &&
               a.variant === b.variant &&
               a.avatarVariant === b.avatarVariant;
      case 'generated':
        return b.type === 'generated' && a.generationRequestId === b.generationRequestId;
      case 'uploaded':
        return b.type === 'uploaded' && a.mediaId === b.mediaId;
      case 'none':
        return b.type === 'none';
      default:
        return false;
    }
  }

  /**
   * Generate hash for this reference
   * Used for caching and comparison
   */
  hash(): string {
    switch (this.props.type) {
      case 'system-default':
        return `system-default:${this.props.variant}${this.props.avatarVariant ? `:${this.props.avatarVariant}` : ''}`;
      case 'generated':
        return `generated:${this.props.generationRequestId}`;
      case 'uploaded':
        return `uploaded:${this.props.mediaId}`;
      case 'none':
        return 'none';
      default:
        // Exhaustive check - should never reach here
        throw new Error('Unknown type');
    }
  }
}