import React from 'react';

import { CommentEntity, CommentReplyEntity } from '@/state/clipStore';
import { FALLBACK_IMAGE_URL } from '@/utils/constants';

export const COMMENT_MAX_LENGTH = 500;
export const COMMENT_MOST_LIKED_SORT_THRESHOLD = 6;

export const COMMENT_REPORT_REASONS = ['spam', 'adult', 'ads', 'ip', 'other'];

export type CommentProps = Omit<
  CommentEntity & CommentReplyEntity,
  'id' | 'clipId' | 'userDisplayName' | 'userHandle' | 'userAvatarUrl'
> & {
  commentId: string;
  clipId?: string;
  avatarUrl?: string;
  handle?: string;
  displayName?: string;
  profileHref?: string;
  isLiked?: boolean;
  isBlocked?: boolean;
};

export function getCommentProps(entity: CommentEntity): CommentProps;
export function getCommentProps(
  entity: CommentReplyEntity,
  parentEntity: CommentEntity
): CommentProps;
export function getCommentProps(
  entity: CommentEntity | CommentReplyEntity,
  parentEntity?: CommentEntity
) {
  const {
    id: commentId,
    entityType = parentEntity?.entityType ?? 'clip', // @TODO: remove after backend schema update
    userAvatarUrl,
    userDisplayName: displayName,
    userHandle: handle,
    ...restProps
  } = entity;
  return {
    ...restProps,
    commentId,
    entityType,
    clipId: 'clipId' in entity ? entity.clipId || undefined : undefined,
    avatarUrl: userAvatarUrl || FALLBACK_IMAGE_URL,
    displayName,
    handle,
    parentId: entity.parentId,
    profileHref: `/@${handle}`,
    isLiked: entity.reactionType === 'like',
    isBlocked: entity.userCommentsBlocked || undefined,
  } satisfies CommentProps;
}

export type CommentContentAnnotation = {
  render: (value: string, key: React.Key) => React.ReactNode;
  start: number;
  end: number;
};

/**
 * Formats text for comment display
 *
 * Applies annotations to the text content, including stamps and user mentions
 *
 * Matches timestamps like "1:23" and converts them to a button that plays the clip at that time.
 * 2+ line breaks creates a new `<p>`
 * 1 line break inserts a `<br>` tag
 */
export function formatCommentText(
  text: string,
  className?: string,
  annotations?: CommentContentAnnotation[]
): React.ReactNode[] {
  if (!text) return [];

  // When there are no annotations, just split into paragraphs
  if (!annotations?.length) {
    // Fast path when no annotations
    return text.split(/\n{2,}/).map((paragraph, idx) => (
      <p key={`p-${idx}`} className={className}>
        {paragraph.split(/\n/).map((line, lineIdx, arr) => (
          <React.Fragment key={`line-${idx}-${lineIdx}`}>
            {line}
            {lineIdx < arr.length - 1 && <br />}
          </React.Fragment>
        ))}
      </p>
    ));
  }

  // Parse the text to find the exact positions of paragraphs
  const paragraphs: { text: string; offset: number }[] = [];
  const paragraphBreakRegex = /\n{2,}/g;
  let offset = 0;
  let match: RegExpExecArray | null;

  while ((match = paragraphBreakRegex.exec(text))) {
    // Add the paragraph before this break
    const paragraphText = text.substring(offset, match.index);
    paragraphs.push({
      text: paragraphText,
      offset,
    });
    offset = match.index + match[0].length;
  }

  // Add the final paragraph if it exists
  if (offset < text.length) {
    paragraphs.push({
      text: text.substring(offset),
      offset,
    });
  }

  // Process each paragraph
  return paragraphs.map(({ text, offset: paragraphOffset }, paragraphIndex) => {
    const lines: { text: string; offset: number }[] = [];
    const linebreakRegex = /\n/g;
    let offset = 0;
    let match: RegExpExecArray | null;

    while ((match = linebreakRegex.exec(text))) {
      // Add the line before this break
      const lineText = text.substring(paragraphOffset, match.index);
      lines.push({
        text: lineText,
        offset,
      });
      offset = match.index + match[0].length;
    }

    // Add the final line if it exists
    if (offset < text.length) {
      lines.push({
        text: text.substring(paragraphOffset),
        offset,
      });
    }

    // Process each line
    const renderedLines = lines.map(({ text, offset }, lineIndex) => {
      // Find annotations that overlap with this line
      const relevantAnnotations = (annotations || []).filter(
        (ann) => ann.start < offset + text.length && ann.end > offset
      );

      // If there are no annotations for this line, return it as-is
      if (relevantAnnotations.length === 0) {
        return (
          <React.Fragment key={`line-${paragraphIndex}-${lineIndex}`}>
            {lineIndex > 0 ? <br /> : null}
            {text}
          </React.Fragment>
        );
      }

      // Process the line with annotations
      const segments = processLineWithAnnotations(
        text,
        offset,
        relevantAnnotations
      );

      return (
        <React.Fragment key={`line-${paragraphIndex}-${lineIndex}`}>
          {segments}
          {lineIndex < lines.length - 1 && <br />}
        </React.Fragment>
      );
    });

    // Return the paragraph with all processed lines
    return (
      <p key={`p-${paragraphIndex}`} className={className}>
        {renderedLines}
      </p>
    );
  });
}

/**
 * Process a line of text with annotations
 */
function processLineWithAnnotations(
  line: string,
  absoluteStartPosition: number,
  annotations: CommentContentAnnotation[]
): React.ReactNode[] {
  // Create a map of positions to annotation events
  type AnnotationEvent = {
    position: number;
    type: 'start' | 'end';
    annotation: CommentContentAnnotation;
  };

  const events: AnnotationEvent[] = [];

  // Create start and end events for each annotation
  annotations.forEach((annotation) => {
    // Convert to relative positions within this line
    const relativeStart = Math.max(0, annotation.start - absoluteStartPosition);
    const relativeEnd = Math.min(
      line.length,
      annotation.end - absoluteStartPosition
    );

    // Only add valid ranges
    if (relativeStart < relativeEnd) {
      events.push({
        position: relativeStart,
        type: 'start',
        annotation,
      });

      events.push({
        position: relativeEnd,
        type: 'end',
        annotation,
      });
    }
  });

  // Sort events by position, then by type (ends before starts at same position)
  events.sort((a, b) => {
    if (a.position !== b.position) {
      return a.position - b.position;
    }
    // If positions are the same, 'end' comes before 'start'
    return a.type === 'end' ? -1 : 1;
  });

  // Process the text with events
  const result: React.ReactNode[] = [];
  let currentPosition = 0;
  const activeAnnotations: CommentContentAnnotation[] = [];

  // Add a final event to ensure we process the end of the line
  events.push({
    position: line.length,
    type: 'end',
    annotation: annotations[0], // Doesn't matter which annotation we use here
  });

  // Process each event
  events.forEach((event) => {
    // If there's text before this event, process it
    if (event.position > currentPosition) {
      const textPart = line.substring(currentPosition, event.position);

      if (activeAnnotations.length === 0) {
        // No active annotations, add as plain text
        result.push(textPart);
      } else {
        // We have active annotations - use the first one that started
        const ann = activeAnnotations[0];
        const key = `${absoluteStartPosition + currentPosition}-${absoluteStartPosition + event.position}`;

        // Render the annotated segment
        result.push(ann.render(textPart, key));
      }

      // Update position
      currentPosition = event.position;
    }

    // Update active annotations based on event type
    if (event.type === 'start') {
      activeAnnotations.push(event.annotation);
      // Sort by start position to maintain consistent order
      activeAnnotations.sort((a, b) => a.start - b.start);
    } else if (event.type === 'end') {
      const idx = activeAnnotations.indexOf(event.annotation);
      if (idx !== -1) {
        activeAnnotations.splice(idx, 1);
      }
    }
  });

  return result;
}

export function extractCurrentWord(
  text: string,
  cursorPosition = 0
): [word: string, start: number, end: number] {
  // Edge cases
  if (!text || cursorPosition < 0 || cursorPosition > text.length)
    return ['', cursorPosition, cursorPosition];
  const index =
    cursorPosition < text.length && text[cursorPosition].match(/\w/)
      ? cursorPosition
      : Math.min(cursorPosition - 1, text.length - 1);
  // In a bunch of spaces
  if (text[index] === ' ') return ['', cursorPosition, cursorPosition];
  // Closest word
  const start = text.lastIndexOf(' ', index) + 1;
  const end = text.indexOf(' ', index);
  const currentWord = text.substring(start, end === -1 ? text.length : end);
  return [currentWord, start, start + currentWord.length];
}
