import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Observable, shareReplay } from 'rxjs';
import { map } from 'rxjs/operators';

/**
 * Клавиша с информацией о том, сколько мс прошло с момента нажатия.
 * Используется для scrub-анимации: чем больше ageMs, тем сильнее затухание.
 */
export interface KeyTapHighlight {
  keyId: string;
}

export interface KeyHighlightDiff {
  /** Newly pressed this frame — animate in with pop. */
  pressed: string[];
  /** Still held from before — static pressed color. */
  held: string[];
  /** Just released this frame — animate back to idle. */
  released: string[];
}

/**
 * Файлы из `public/svg/visual/*.svg` — URL в приложении `/svg/visual/...`.
 * `apiBaseUrlInterceptor` пропускает пути с префиксом `/svg/` (STATIC_ASSET_PREFIXES).
 */
export const KEYBOARD_SVG_PATH = '/svg/visual/keyboard.svg';

/** Отдельный блок стрелок (рядом с мышью в интерактивном режиме). */
export const ARROW_KEYS_SVG_PATH = '/svg/visual/arrow-keys.svg';

@Injectable({ providedIn: 'root' })
export class KeyboardSvgHighlightService {
  private readonly sanitizer = inject(DomSanitizer);
  private readonly http = inject(HttpClient);

  private readonly baseSvgCache = new Map<string, Observable<string>>();

  private baseSvg$(path: string): Observable<string> {
    let cached = this.baseSvgCache.get(path);
    if (!cached) {
      cached = this.http.get(path, { responseType: 'text' }).pipe(
        shareReplay({ bufferSize: 1, refCount: false }),
      );
      this.baseSvgCache.set(path, cached);
    }
    return cached;
  }

  /**
   * Interactive timeline mode: animates only the changed keys.
   * Pressed keys pop in, held keys stay static, released keys fade back to idle.
   *
   * SECURITY: bypassSecurityTrustHtml используется для SVG, загруженных из собственного public/.
   * НЕ передавать пользовательский контент через этот метод — XSS-риск.
   */
  svgWithKeyDiff(diff: KeyHighlightDiff, svgPath: string = KEYBOARD_SVG_PATH): Observable<SafeHtml> {
    return this.baseSvg$(svgPath).pipe(
      map((svg) => this.injectHighlightDiff(svg, diff)),
      map((html) => this.sanitizer.bypassSecurityTrustHtml(html)),
    );
  }

  /**
   * Интерактивный режим: каждая клавиша подсвечивается пропорционально своему возрасту (ageMs).
   * Используется CSS-scrubbing: анимация поставлена на паузу, а animation-delay смещает её
   * в нужную точку, чтобы отобразить состояние «только что нажато» → «гаснет».
   */
  svgWithKeyTaps(taps: KeyTapHighlight[], svgPath: string = KEYBOARD_SVG_PATH): Observable<SafeHtml> {
    return this.baseSvg$(svgPath).pipe(
      map((svg) => this.injectKeyTaps(svg, taps)),
      map((html) => this.sanitizer.bypassSecurityTrustHtml(html)),
    );
  }

  svgWithHighlight(keyIds: string[] | null, animated = true, svgPath: string = KEYBOARD_SVG_PATH): Observable<SafeHtml> {
    return this.baseSvg$(svgPath).pipe(
      map((svg) => this.injectHighlight(svg, keyIds ?? [], animated)),
      map((html) => this.sanitizer.bypassSecurityTrustHtml(html)),
    );
  }

  private injectHighlightDiff(svgText: string, diff: KeyHighlightDiff): string {
    const pressed = this.validIds(diff.pressed);
    const held = this.validIds(diff.held);
    const released = this.validIds(diff.released);

    if (pressed.length === 0 && held.length === 0 && released.length === 0) {
      return svgText;
    }

    const rules: string[] = [];

    for (const id of pressed) {
      const s = id.slice(2);
      rules.push(
        `#${id}{fill:var(--sg-keyboard-key-pressed-fill)!important;stroke:none!important;transform-box:fill-box;transform-origin:center;animation:sgKeyPress 160ms cubic-bezier(0.33,1,0.68,1);}`,
      );
      rules.push(
        `[id^="T_${s}"]{fill:var(--sg-keyboard-key-pressed-ink)!important;animation:sgKeyFadeIn 160ms cubic-bezier(0.33,1,0.68,1);}`,
      );
      rules.push(
        `#S_${s} path,#S_${s} line,#S_${s} polyline,#S_${s} rect{stroke:var(--sg-keyboard-key-pressed-ink)!important;fill:none!important;animation:sgKeyFadeIn 160ms cubic-bezier(0.33,1,0.68,1);}`,
      );
    }

    for (const id of held) {
      const s = id.slice(2);
      rules.push(
        `#${id}{fill:var(--sg-keyboard-key-pressed-fill)!important;stroke:none!important;}`,
      );
      rules.push(`[id^="T_${s}"]{fill:var(--sg-keyboard-key-pressed-ink)!important;}`);
      rules.push(
        `#S_${s} path,#S_${s} line,#S_${s} polyline,#S_${s} rect{stroke:var(--sg-keyboard-key-pressed-ink)!important;fill:none!important;}`,
      );
    }

    for (const id of released) {
      const s = id.slice(2);
      // No fill override — lets SVG's own per-group CSS restore the correct idle color
      // (GlyphKey/ModifKey/OtherKey each have their own fill token).
      rules.push(
        `#${id}{transform-box:fill-box;transform-origin:center;animation:sgKeyRelease 200ms ease;}`,
      );
      rules.push(`[id^="T_${s}"]{animation:sgKeyReleaseInk 200ms ease;}`);
      rules.push(
        `#S_${s} path,#S_${s} line,#S_${s} polyline,#S_${s} rect{animation:sgKeyReleaseInk 200ms ease;}`,
      );
    }

    rules.push(
      `@keyframes sgKeyPress{0%{transform:scale(0.88);opacity:0.5;}55%{transform:scale(1.03);}100%{transform:scale(1);opacity:1;}}`,
    );
    rules.push(`@keyframes sgKeyFadeIn{0%{opacity:0.35;}100%{opacity:1;}}`);
    rules.push(
      `@keyframes sgKeyRelease{0%{transform:scale(1);}40%{transform:scale(0.96);}100%{transform:scale(1);}}`,
    );
    rules.push(
      `@keyframes sgKeyReleaseInk{0%{fill:var(--sg-keyboard-key-pressed-ink);}100%{fill:var(--sg-keyboard-ink-soft);}}`,
    );

    const styleBlock = `<style type="text/css"><![CDATA[${rules.join('')}]]></style>`;
    return svgText.replace(/<\/svg>/i, `${styleBlock}</svg>`);
  }

  private validIds(ids: string[]): string[] {
    return [...new Set(ids.filter((id) => /^K_kb[0-9a-z]+$/i.test(id)))];
  }

  private injectHighlight(svgText: string, keyIds: string[], animated: boolean): string {
    const valid = this.validIds(keyIds);
    if (valid.length === 0) {
      return svgText;
    }
    const rules: string[] = [];
    for (const id of valid) {
      const suffix = id.slice(2);
      if (animated) {
        rules.push(
          `#${id}{fill:var(--sg-keyboard-key-pressed-fill)!important;stroke:none!important;transform-box:fill-box;transform-origin:center;animation:sgInputHighlightPop var(--sg-input-highlight-duration) var(--sg-input-highlight-easing);}`,
        );
        rules.push(
          `[id^="T_${suffix}"]{fill:var(--sg-keyboard-key-pressed-ink)!important;animation:sgInputHighlightFade var(--sg-input-highlight-duration) var(--sg-input-highlight-easing);}`,
        );
        rules.push(
          `#S_${suffix} path,#S_${suffix} line,#S_${suffix} polyline,#S_${suffix} rect{stroke:var(--sg-keyboard-key-pressed-ink)!important;fill:none!important;transform-box:fill-box;transform-origin:center;animation:sgInputHighlightFade var(--sg-input-highlight-duration) var(--sg-input-highlight-easing);}`,
        );
      } else {
        rules.push(
          `#${id}{fill:var(--sg-keyboard-key-pressed-fill)!important;stroke:none!important;}`,
        );
        rules.push(
          `[id^="T_${suffix}"]{fill:var(--sg-keyboard-key-pressed-ink)!important;}`,
        );
        rules.push(
          `#S_${suffix} path,#S_${suffix} line,#S_${suffix} polyline,#S_${suffix} rect{stroke:var(--sg-keyboard-key-pressed-ink)!important;fill:none!important;}`,
        );
      }
    }
    if (animated) {
      rules.push(
        `@keyframes sgInputHighlightPop{0%{opacity:.45;transform:scale(var(--sg-input-highlight-from-scale));}100%{opacity:1;transform:scale(1);}}`,
      );
      rules.push(`@keyframes sgInputHighlightFade{0%{opacity:.45;}100%{opacity:1;}}`);
    }
    const styleBlock = `<style type="text/css"><![CDATA[${rules.join('')}]]></style>`;
    return svgText.replace(/<\/svg>/i, `${styleBlock}</svg>`);
  }

  private injectKeyTaps(svgText: string, taps: KeyTapHighlight[]): string {
    const valid = taps.filter((t) => /^K_kb[0-9a-z]+$/i.test(t.keyId));
    if (valid.length === 0) {
      return svgText;
    }

    const rules: string[] = [];

    for (const { keyId } of valid) {
      const s = keyId.slice(2);
      rules.push(`#${keyId}{fill:var(--sg-keyboard-key-pressed-fill)!important;stroke:none!important;}`);
      rules.push(`[id^="T_${s}"]{fill:var(--sg-keyboard-key-pressed-ink)!important;}`);
      rules.push(`#S_${s} path,#S_${s} line,#S_${s} polyline,#S_${s} rect{stroke:var(--sg-keyboard-key-pressed-ink)!important;fill:none!important;}`);
    }

    const styleBlock = `<style type="text/css"><![CDATA[${rules.join('')}]]></style>`;
    return svgText.replace(/<\/svg>/i, `${styleBlock}</svg>`);
  }
}
