import {inject, Injectable} from '@angular/core';
import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object';
import {ColoredNameSegment} from '@pages/altitude/chat/objects/colored-name-segment.object';
import {ChatService} from "./chat.service";

@Injectable({providedIn: 'root'})
export class NameFormatService {

  private readonly chatService: ChatService = inject(ChatService);

  public getDisplayName(channel: ChatChannel): ColoredNameSegment[] {
    return this.parseColoredName(this.getNameFromType(channel));
  }

  private getNameFromType(channel: ChatChannel): string {
    if (channel.type === 'PARTY') {
      return this.chatService.partieMap().get(channel.name) ?? channel.name;
    }
    if (channel.type === 'DM' || channel.type === 'SPY') {
      return this.chatService.userNameMap().get(channel.name) ?? channel.name;
    }
    return channel.name;
  }

  private parseColoredName(name: string): ColoredNameSegment[] {
    const markerRegex = /\{#([0-9a-fA-F]{6})(<>|[<>]?)}/g;
    const segments: ColoredNameSegment[] = [];
    let currentColor: string | undefined;
    let gradientStartColor: string | undefined;
    let gradientStartSegmentIndex: number | undefined;
    let lastIndex = 0;
    let match: RegExpExecArray | null;

    while ((match = markerRegex.exec(name)) !== null) {
      this.addColoredTextSegment(segments, name.slice(lastIndex, match.index), currentColor);

      const markerColor = `#${match[1]}`;
      const markerType = match[2];

      if (markerType === '>') {
        currentColor = markerColor;
        gradientStartColor = markerColor;
        gradientStartSegmentIndex = segments.length;
      } else if (markerType === '<') {
        if (gradientStartColor !== undefined && gradientStartSegmentIndex !== undefined) {
          this.applyGradient(segments, gradientStartSegmentIndex, gradientStartColor, markerColor);
        }
        currentColor = markerColor;
        gradientStartColor = undefined;
        gradientStartSegmentIndex = undefined;
      } else if (markerType === '<>') {
        if (gradientStartColor !== undefined && gradientStartSegmentIndex !== undefined) {
          this.applyGradient(segments, gradientStartSegmentIndex, gradientStartColor, markerColor);
        }
        currentColor = markerColor;
        gradientStartColor = markerColor;
        gradientStartSegmentIndex = segments.length;
      } else {
        currentColor = markerColor;
        gradientStartColor = undefined;
        gradientStartSegmentIndex = undefined;
      }

      lastIndex = markerRegex.lastIndex;
    }

    this.addColoredTextSegment(segments, name.slice(lastIndex), currentColor);

    return segments.length > 0 ? segments : [{text: name}];
  }

  private addColoredTextSegment(segments: ColoredNameSegment[], text: string, color?: string): void {
    if (text.length === 0) {
      return;
    }

    segments.push({text, color});
  }

  private applyGradient(
    segments: ColoredNameSegment[],
    startSegmentIndex: number,
    startColor: string,
    endColor: string
  ): void {
    const text = segments.slice(startSegmentIndex).map((segment) => segment.text).join('');
    const length = text.length;
    const start = this.hexToRgb(startColor);
    const end = this.hexToRgb(endColor);

    if (length === 0 || start === null || end === null) {
      return;
    }

    const gradientSegments: ColoredNameSegment[] = [];

    for (let i = 0; i < length; i++) {
      const ratio = length === 1 ? 0 : i / (length - 1);
      gradientSegments.push({
        text: text[i],
        color: this.rgbToHex(
          start.r + (end.r - start.r) * ratio,
          start.g + (end.g - start.g) * ratio,
          start.b + (end.b - start.b) * ratio
        )
      });
    }

    segments.splice(startSegmentIndex, segments.length - startSegmentIndex, ...gradientSegments);
  }

  private hexToRgb(hex: string): { r: number; g: number; b: number } | null {
    const match = /^#?([0-9a-fA-F]{6})$/.exec(hex);
    if (!match) {
      return null;
    }

    const value = Number.parseInt(match[1], 16);
    return {
      r: (value >> 16) & 255,
      g: (value >> 8) & 255,
      b: value & 255
    };
  }

  private rgbToHex(r: number, g: number, b: number): string {
    return `#${[r, g, b]
      .map((value) => Math.round(value).toString(16).padStart(2, '0'))
      .join('')}`;
  }
}
