import { Injectable } from '@angular/core';

export interface SelectionRange {
  start: number;
  end: number;
}

export interface ComposeResult {
  text: string;
  selectionStart: number;
  selectionEnd: number;
}

@Injectable({ providedIn: 'root' })
export class ChatMarkdownService {
  applyInline(content: string, selection: SelectionRange, token: string): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const newText = `${before}${token}${selected}${token}${after}`;
    const cursor = selected.length === 0 ? before.length + token.length : before.length + token.length + selected.length + token.length;

    return { text: newText, selectionStart: cursor, selectionEnd: cursor };
  }

  applyPrefix(content: string, selection: SelectionRange, prefix: string): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const lines = selected.split('\n').map((line) => `${prefix}${line}`);
    const newSelected = lines.join('\n');
    const text = `${before}${newSelected}${after}`;
    const cursor = before.length + newSelected.length;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyHeading(content: string, selection: SelectionRange, level: number): ComposeResult {
    const hashes = '#'.repeat(Math.max(1, Math.min(6, level)));
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const needsLeadingNewline = before.length > 0 && !before.endsWith('\n');
    const needsTrailingNewline = after.length > 0 && !after.startsWith('\n');
    const block = `${needsLeadingNewline ? '\n' : ''}${hashes} ${selected}${needsTrailingNewline ? '\n' : ''}`;
    const text = `${before}${block}${after}`;
    const cursor = before.length + block.length - (needsTrailingNewline ? 1 : 0);

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyOrderedList(content: string, selection: SelectionRange): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const lines = selected.split('\n').map((line, index) => `${index + 1}. ${line}`);
    const newSelected = lines.join('\n');
    const text = `${before}${newSelected}${after}`;
    const cursor = before.length + newSelected.length;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyCodeBlock(content: string, selection: SelectionRange): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const fenced = selected.length === 0 ? '```\n\n```\n\n' : `\`\`\`\n${selected}\n\`\`\`\n\n`;
    const text = `${before}${fenced}${after}`;
    const cursor = selected.length === 0 ? before.length + 4 : before.length + fenced.length;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyLink(content: string, selection: SelectionRange): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const link = `[${selected}]()`;
    const text = `${before}${link}${after}`;
    const cursor = selected.length === 0 ? before.length + 1 : before.length + 1 + selected.length + 2;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyImage(content: string, selection: SelectionRange): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const selected = content.slice(start, end);
    const after = content.slice(end);
    const img = `![${selected}]()`;
    const text = `${before}${img}${after}`;
    const cursor = selected.length === 0 ? before.length + 2 : before.length + 2 + selected.length + 2;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  applyHorizontalRule(content: string, selection: SelectionRange): ComposeResult {
    const { start, end } = selection;
    const before = content.slice(0, start);
    const after = content.slice(end);
    const hr = '\n\n---\n\n';
    const text = `${before}${hr}${after}`;
    const cursor = before.length + hr.length;

    return { text, selectionStart: cursor, selectionEnd: cursor };
  }

  appendImageMarkdown(content: string): string {
    const imageUrlRegex = /(https?:\/\/[^\s)]+?\.(?:png|jpe?g|gif|webp|svg|bmp|tiff)(?:\?[^\s)]*)?)/gi;
    const urls = new Set<string>();

    let match: RegExpExecArray | null;

    const text = content;

    while ((match = imageUrlRegex.exec(text)) !== null) {
      urls.add(match[1]);
    }

    if (urls.size === 0)
      return content;

    let append = '';

    for (const url of urls) {
      const alreadyEmbedded = new RegExp(`!\\[[^\\]]*\\]\\(\\s*${this.escapeRegex(url)}\\s*\\)`, 'i').test(text);

      if (!alreadyEmbedded) {
        append += `\n![](${url})`;
      }
    }

    return append ? content + append : content;
  }

  private escapeRegex(str: string): string {
    return str.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
  }
}
