import {
  Directive,
  ElementRef,
  HostListener,
  effect,
  inject,
  input,
  OnDestroy
} from '@angular/core';

import { ExternalLinkService } from '../../../core/platform';
import { ElementPickerService } from '../application/services/element-picker.service';
import { ThemeRegistryService } from '../application/services/theme-registry.service';
import { ThemeService } from '../application/services/theme.service';
import { applyThemeStyleDeclaration } from './theme-style-application.logic';

function looksLikeImageReference(value: string): boolean {
  return value.startsWith('url(')
    || value.startsWith('http://')
    || value.startsWith('https://')
    || value.startsWith('/')
    || value.startsWith('./');
}

@Directive({
  selector: '[appThemeNode]',
  standalone: true
})
export class ThemeNodeDirective implements OnDestroy {
  readonly themeKey = input.required<string>({ alias: 'appThemeNode' });

  private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly theme = inject(ThemeService);
  private readonly registry = inject(ThemeRegistryService);
  private readonly picker = inject(ElementPickerService);
  private readonly externalLinks = inject(ExternalLinkService);

  private appliedStyleKeys = new Set<string>();
  private appliedClasses = new Set<string>();
  private originalTextContent = new WeakMap<HTMLElement, string>();
  private registeredKey: string | null = null;

  constructor() {
    effect(() => {
      const key = this.themeKey();
      const definition = this.registry.getDefinition(key);
      const host = this.host.nativeElement;

      host.dataset['themeKey'] = key;
      host.dataset['themeLabel'] = definition?.label ?? key;

      if (this.registeredKey && this.registeredKey !== key) {
        this.registry.unregisterHost(this.registeredKey, host);
      }

      if (this.registeredKey !== key) {
        this.registry.registerHost(key, host);
        this.registeredKey = key;
      }
    });

    effect(() => {
      this.applyThemeStyles();
      this.applyAnimationClasses();
      this.applyTextOverride();
      this.applyIconOverride();
      this.applyLinkState();
      this.applyPickerState();
    });
  }

  @HostListener('keydown.enter', ['$event'])
  onEnterKey(event: Event): void {
    this.openConfiguredLink(event);
  }

  @HostListener('click', ['$event'])
  onClick(event: MouseEvent): void {
    this.openConfiguredLink(event);
  }

  ngOnDestroy(): void {
    if (this.registeredKey) {
      this.registry.unregisterHost(this.registeredKey, this.host.nativeElement);
      this.registeredKey = null;
    }

    this.clearAppliedStyles();
    this.clearAppliedClasses();
    this.restoreTextTarget();
    this.resetIconTarget();
  }

  private applyThemeStyles(): void {
    const styles = this.theme.getHostStyles(this.themeKey());

    this.clearAppliedStyles();

    for (const [styleKey, styleValue] of Object.entries(styles)) {
      this.appliedStyleKeys.add(applyThemeStyleDeclaration(this.host.nativeElement, styleKey, styleValue));
    }
  }

  private applyAnimationClasses(): void {
    this.clearAppliedClasses();

    const animationClass = this.theme.getAnimationClass(this.themeKey());

    if (!animationClass) {
      return;
    }

    this.host.nativeElement.classList.add(animationClass);
    this.appliedClasses.add(animationClass);
  }

  private applyTextOverride(): void {
    const definition = this.registry.getDefinition(this.themeKey());
    const textTarget = this.host.nativeElement.querySelector<HTMLElement>('[data-theme-slot="text"]');

    if (!definition?.supportsTextOverride || !textTarget) {
      return;
    }

    if (!this.originalTextContent.has(textTarget)) {
      this.originalTextContent.set(textTarget, textTarget.textContent ?? '');
    }

    textTarget.textContent = this.theme.getTextOverride(this.themeKey()) ?? this.originalTextContent.get(textTarget) ?? '';
  }

  private applyIconOverride(): void {
    const definition = this.registry.getDefinition(this.themeKey());
    const iconTarget = this.host.nativeElement.querySelector<HTMLElement>('[data-theme-slot="icon"]');
    const iconValue = this.theme.getIcon(this.themeKey());

    if (!definition?.supportsIcon || !iconTarget) {
      return;
    }

    if (!iconValue) {
      this.resetIconTarget();
      return;
    }

    iconTarget.dataset['themeVisible'] = 'true';

    if (looksLikeImageReference(iconValue)) {
      const imageReference = iconValue.startsWith('url(')
        ? iconValue
        : `url('${iconValue}')`;

      iconTarget.style.backgroundImage = imageReference;
      iconTarget.textContent = '';
    } else {
      iconTarget.style.backgroundImage = 'none';
      iconTarget.textContent = iconValue.slice(0, 2).toUpperCase();
    }
  }

  private applyLinkState(): void {
    const definition = this.registry.getDefinition(this.themeKey());
    const link = this.theme.getLink(this.themeKey());
    const supportsLink = definition?.supportsLink && !!link;

    this.host.nativeElement.dataset['themeLinked'] = supportsLink ? 'true' : 'false';

    if (!supportsLink) {
      this.host.nativeElement.removeAttribute('tabindex');
      this.host.nativeElement.removeAttribute('role');
      return;
    }

    this.host.nativeElement.setAttribute('tabindex', '0');
    this.host.nativeElement.setAttribute('role', 'link');
  }

  private applyPickerState(): void {
    const definition = this.registry.getDefinition(this.themeKey());
    const isHovered = this.picker.hoveredKey() === this.themeKey();
    const isSelected = this.picker.selectedKey() === this.themeKey();
    const isActive = this.picker.isPicking() && !!definition?.pickerVisible;

    this.host.nativeElement.dataset['themePickerActive'] = isActive ? 'true' : 'false';
    this.host.nativeElement.dataset['themePickerHovered'] = isHovered ? 'true' : 'false';
    this.host.nativeElement.dataset['themePickerSelected'] = isSelected ? 'true' : 'false';
  }

  private openConfiguredLink(event: Event): void {
    if (this.picker.isPicking()) {
      return;
    }

    const definition = this.registry.getDefinition(this.themeKey());
    const link = this.theme.getLink(this.themeKey());

    if (!definition?.supportsLink || !link) {
      return;
    }

    const target = event.target;

    if (target instanceof Element && target.closest('button, input, textarea, select, a, [role="button"]')) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();
    this.externalLinks.open(link);
  }

  private clearAppliedStyles(): void {
    for (const styleKey of this.appliedStyleKeys) {
      this.host.nativeElement.style.removeProperty(styleKey);
    }

    this.appliedStyleKeys.clear();
  }

  private clearAppliedClasses(): void {
    for (const className of this.appliedClasses) {
      this.host.nativeElement.classList.remove(className);
    }

    this.appliedClasses.clear();
  }

  private restoreTextTarget(): void {
    const textTarget = this.host.nativeElement.querySelector<HTMLElement>('[data-theme-slot="text"]');

    if (!textTarget || !this.originalTextContent.has(textTarget)) {
      return;
    }

    textTarget.textContent = this.originalTextContent.get(textTarget) ?? '';
  }

  private resetIconTarget(): void {
    const iconTarget = this.host.nativeElement.querySelector<HTMLElement>('[data-theme-slot="icon"]');

    if (!iconTarget) {
      return;
    }

    iconTarget.dataset['themeVisible'] = 'false';
    iconTarget.style.backgroundImage = 'none';
    iconTarget.textContent = '';
  }
}
