import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild, inject, signal } from '@angular/core';
import { TranslateService } from '../../../../i18n/translate.service';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { validateHtml } from '../../schema/validators/primitives';
import { CodeEditorComponent } from '../../../../shared/ui/code-editor/code-editor.component';
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
import { AppIconName } from '../../../../shared/ui/icon/icon-registry';

export interface HtmlEditorToolbarCommand {
  id: string;
  label: string;
  command: string;
  value?: string;
  /** Lucide icon name (see icon-registry.ts); when set the icon renders instead of the label. */
  icon?: AppIconName;
  /** i18n key for the tooltip/aria-label. */
  titleKey?: string;
}

export const HTML_EDITOR_TOOLBAR: HtmlEditorToolbarCommand[] = [
  { id: 'bold', label: 'B', command: 'bold', titleKey: 'builder.htmlToolBold' },
  { id: 'italic', label: 'I', command: 'italic', titleKey: 'builder.htmlToolItalic' },
  { id: 'underline', label: 'U', command: 'underline', titleKey: 'builder.htmlToolUnderline' },
  { id: 'h2', label: 'H2', command: 'formatBlock', value: 'H2', titleKey: 'builder.htmlToolHeading' },
  { id: 'h3', label: 'H3', command: 'formatBlock', value: 'H3', titleKey: 'builder.htmlToolSubheading' },
  { id: 'ul', label: '', icon: 'list', command: 'insertUnorderedList', titleKey: 'builder.htmlToolBulletList' },
  { id: 'ol', label: '1.', command: 'insertOrderedList', titleKey: 'builder.htmlToolNumberedList' },
  { id: 'link', label: '', icon: 'link', command: 'createLink', titleKey: 'builder.htmlToolLink' },
  { id: 'image', label: '', icon: 'image', command: 'insertImage', titleKey: 'builder.htmlToolImage' },
  { id: 'table', label: '', icon: 'table', command: 'insertHTML', value: '<table><tr><td>&nbsp;</td><td>&nbsp;</td></tr></table>', titleKey: 'builder.htmlToolTable' },
  { id: 'hr', label: '', icon: 'minus', command: 'insertHorizontalRule', titleKey: 'builder.htmlToolDivider' },
  { id: 'codeblock', label: '', icon: 'code', command: 'formatBlock', value: 'PRE', titleKey: 'builder.htmlToolCodeBlock' },
  { id: 'embed', label: '', icon: 'video', command: 'insertEmbed', titleKey: 'builder.htmlToolEmbed' },
];

/** Visual grouping for the toolbar: text style | headings | lists | insert | advanced. */
export const HTML_EDITOR_TOOLBAR_GROUPS: string[][] = [
  ['bold', 'italic', 'underline'],
  ['h2', 'h3'],
  ['ul', 'ol'],
  ['link', 'image', 'table', 'hr'],
  ['codeblock', 'embed'],
];

@Component({
  selector: 'app-marketplace-html-editor',
  standalone: true,
  imports: [TranslatePipe, CodeEditorComponent, IconComponent],
  templateUrl: './marketplace-html-editor.component.html',
  styleUrls: ['./marketplace-html-editor.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class MarketplaceHtmlEditorComponent implements OnChanges, AfterViewInit {
  private readonly translate = inject(TranslateService);

  @Input() html = '';
  @Output() htmlChange = new EventEmitter<string>();
  @ViewChild('surface', { static: true }) surface!: ElementRef<HTMLDivElement>;

  readonly toolbar = HTML_EDITOR_TOOLBAR;
  readonly toolbarGroups = HTML_EDITOR_TOOLBAR_GROUPS.map(group =>
    group.map(id => HTML_EDITOR_TOOLBAR.find(item => item.id === id)!).filter(Boolean)
  );
  readonly showCode = signal(false);
  readonly codeValue = signal('');
  readonly codeError = signal<string | null>(null);

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['html']) {
      this.syncSurfaceFromInput();
    }
  }

  ngAfterViewInit(): void {
    // Safety net: on the very first change detection pass, ngOnChanges can run
    // before the static ViewChild is resolved, so re-sync once the view is ready.
    this.syncSurfaceFromInput();
  }

  private syncSurfaceFromInput(): void {
    if (this.surface && this.surface.nativeElement.innerHTML !== (this.html || '')) {
      this.surface.nativeElement.innerHTML = this.html || '';
    }
  }

  runCommand(item: HtmlEditorToolbarCommand): void {
    this.surface.nativeElement.focus();
    if (item.command === 'createLink') {
      const url = window.prompt(this.translate.t('builder.promptLinkUrl'));
      if (!url) {
        return;
      }
      document.execCommand('createLink', false, url);
    } else if (item.command === 'insertImage') {
      const url = window.prompt(this.translate.t('builder.promptImageUrl'));
      if (!url) {
        return;
      }
      document.execCommand('insertImage', false, url);
    } else if (item.command === 'insertEmbed') {
      const url = window.prompt(this.translate.t('builder.promptEmbedUrl'));
      if (!url) {
        return;
      }
      // Sandboxed iframe: no same-origin/script escalation into the editor
      // page itself. The editor already emits raw, unsanitized HTML by design
      // (FACTS PE-...-0004) - sanitization happens at storefront render time
      // (StaticPageComponent runs everything through DomSanitizer).
      document.execCommand(
        'insertHTML',
        false,
        `<iframe src="${this.escapeAttribute(url)}" sandbox="allow-scripts allow-same-origin" loading="lazy" style="width:100%;aspect-ratio:16/9;border:0"></iframe>`,
      );
    } else {
      document.execCommand(item.command, false, item.value);
    }
    this.emitChange();
  }

  onInput(): void {
    this.emitChange();
  }

  toggleCode(): void {
    if (!this.showCode()) {
      this.codeValue.set(this.surface.nativeElement.innerHTML);
      this.codeError.set(null);
      this.showCode.set(true);
      return;
    }
    // Validate raw HTML before committing it back to the visual surface -
    // malformed markup (an unclosed/mismatched tag) stays in code mode with
    // an inline error rather than silently corrupting the visual editor.
    const result = validateHtml(this.codeValue());
    if (!result.ok) {
      this.codeError.set(result.error ?? this.translate.t('builder.htmlEditorInvalidHtml'));
      return;
    }
    this.codeError.set(null);
    this.surface.nativeElement.innerHTML = this.codeValue();
    this.showCode.set(false);
    this.emitChange();
  }

  updateCode(value: string): void {
    this.codeValue.set(value);
    if (this.codeError()) {
      this.codeError.set(null);
    }
  }

  private escapeAttribute(value: string): string {
    return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;');
  }

  private emitChange(): void {
    this.htmlChange.emit(this.surface.nativeElement.innerHTML);
  }
}
