import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core';
import { SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { LabelsService, NavigationService, SDKService } from '@flaps/core';
import { ModalConfig } from '@guillotinaweb/pastanaga-angular';
import {
  Classification,
  ExtractConfig,
  FIELD_TYPE,
  FieldId,
  FileField,
  FileFieldData,
  IError,
  LinkFieldData,
  MessageAttachment,
  Resource,
  TextField,
  trimLabelSets,
  TypeParagraph,
} from '@nuclia/core';
import { SisModalService } from '@nuclia/sistema';
import {
  BehaviorSubject,
  combineLatest,
  filter,
  map,
  Observable,
  of,
  ReplaySubject,
  Subject,
  switchMap,
  take,
} from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import {
  DATA_AUGMENTATION_ERROR,
  getErrors,
  getParagraphsWithImages,
  ParagraphWithTextAndClassifications,
  ParagraphWithTextAndImage,
  Thumbnail,
} from '../edit-resource.helpers';
import { EditResourceService } from '../edit-resource.service';
import { ParagraphService } from '../paragraph.service';
import { ResourceNavigationService } from '../resource-navigation.service';
import { PreviewService } from './preview.service';
import { WarningModalComponent } from './warning-modal/warning-modal.component';

@Component({
  templateUrl: './preview.component.html',
  styleUrls: ['../common-page-layout.scss', './preview.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: false,
})
export class PreviewComponent implements OnInit, OnDestroy {
  private route: ActivatedRoute = inject(ActivatedRoute);
  private router: Router = inject(Router);
  private labelsService = inject(LabelsService);
  private modalService = inject(SisModalService);
  private navigationService = inject(NavigationService);

  unsubscribeAll = new Subject<void>();
  isArag = this.route.data.pipe(
    map((data) => data['mode'] === 'arag'),
    takeUntil(this.unsubscribeAll),
  );
  paragraphs = this.paragraphService.paragraphList as Observable<ParagraphWithTextAndClassifications[]>;
  jsonTextField = this.editResourceService.currentFieldData.pipe(
    map((field) =>
      !!field && !!field.value && (field.value as TextField).format === 'JSON'
        ? JSON.stringify(JSON.parse((field!.value as TextField).body), null, 2)
        : '',
    ),
  );
  extractConfigId: Observable<string | undefined> = this.editResourceService.currentFieldData.pipe(
    map((field) => (field?.value as FileField)?.extract_strategy),
  );
  splitConfigId: Observable<string | undefined> = this.editResourceService.currentFieldData.pipe(
    map((field) => (field?.value as FileField)?.split_strategy),
  );
  extractConfig: Observable<ExtractConfig | undefined> = this.extractConfigId.pipe(
    filter((configId) => typeof configId === 'string'),
    switchMap((configId) => this.editResourceService.extractStrategies.pipe(map((strategies) => strategies[configId]))),
  );
  splitConfig: Observable<ExtractConfig | undefined> = this.splitConfigId.pipe(
    filter((configId) => typeof configId === 'string'),
    switchMap((configId) => this.editResourceService.splitStrategies.pipe(map((strategies) => strategies[configId]))),
  );
  aiModelsUrl = this.navigationService.kbUrl.pipe(map((kbUrl) => `${kbUrl}/ai-models`));

  viewerWidget: Observable<SafeHtml> = this.previewService.viewerWidget.pipe(takeUntil(this.unsubscribeAll));

  loaded = false;
  loadingPreview = false;
  errors: IError[] = [];
  dataAugmentationErrors: IError[] = [];

  resource: Observable<Resource> = this.editResource.resource.pipe(
    filter((resource) => !!resource),
    map((resource) => resource as Resource),
  );
  isOnResourcePage: Observable<any> = this.route.params.pipe(
    map((params) => !params['fieldId'] && !params['fieldType']),
  );
  fieldId: Observable<FieldId> = this.route.params.pipe(
    filter((params) => !!params['fieldType'] && !!params['fieldId']),
    map((params) => {
      const field: FieldId = { field_id: params['fieldId'], field_type: params['fieldType'] };
      this.editResource.setCurrentField(field);
      return field;
    }),
  );
  extraMetadata = this.resource.pipe(map((resource) => JSON.stringify(resource.extra?.metadata, null, 2)));
  extraMetadataFullscreen = false;
  summary = this.resource.pipe(map((resource) => (resource.summary || '').replace(/\n/g, '<br>')));

  private _noField = new ReplaySubject<boolean>(1);
  noField: Observable<boolean> = this._noField.asObservable();

  thumbnails: Observable<Thumbnail[]> = this.resource.pipe(
    switchMap((res) => this.editResource.getThumbnails(this.editResource.getThumbnailsAndImages(res))),
  );
  hasThumbnail: Observable<boolean> = this.thumbnails.pipe(map((thumbnails) => thumbnails.length > 0));

  selectedTypes = new BehaviorSubject<TypeParagraph[]>([]);
  paragraphTypes = this.paragraphs.pipe(
    map((paragraphs) =>
      paragraphs
        .filter((paragraph) => paragraph.kind)
        .map((paragraph) => paragraph.kind as TypeParagraph)
        .reduce((kinds, kind) => (kinds.includes(kind) ? kinds : [...kinds, kind]), [] as TypeParagraph[]),
    ),
  );
  selectedLabels = new BehaviorSubject<Classification[]>([]);
  labelSets = combineLatest([this.paragraphs, this.labelsService.textBlockLabelSets]).pipe(
    map(([paragraphs, labelSets]) => {
      const pararaphsLabels = paragraphs.reduce((acc, paragraph) => {
        const paragraphLabels = paragraph.activeClassifications.filter(
          (label) => !acc.some((item) => item.labelset === label.labelset && item.label === label.label),
        );
        return acc.concat(paragraphLabels);
      }, [] as Classification[]);
      return trimLabelSets(labelSets || {}, pararaphsLabels);
    }),
  );
  hasFilters = combineLatest([this.labelSets, this.paragraphTypes]).pipe(
    map(([labelSets, types]) => Object.keys(labelSets).length > 0 || types.length > 1),
  );
  hasSelectedFilters = combineLatest([this.selectedLabels, this.selectedTypes]).pipe(
    map(([labels, types]) => labels.length > 0 || types.length > 0),
  );

  renderedParagraphs: Observable<(ParagraphWithTextAndImage & { url?: Observable<string> })[]> = combineLatest([
    this.paragraphs,
    this.fieldId,
    this.editResourceService.currentFieldData,
  ]).pipe(
    map(([paragraphs, fieldType, fieldData]) => {
      paragraphs = paragraphs.sort((a, b) => (a.start || 0) - (b.start || 0));
      if ([FIELD_TYPE.file, FIELD_TYPE.link].includes(fieldType.field_type)) {
        return getParagraphsWithImages(paragraphs, fieldData as FileFieldData | LinkFieldData);
      } else {
        return paragraphs;
      }
    }),
    map((paragraphs) => {
      return (paragraphs as ParagraphWithTextAndImage[]).map((paragraph) => {
        if (paragraph.imagePath) {
          return { ...paragraph, url: this.getGeneratedFileUrl(paragraph.imagePath) };
        } else {
          return paragraph;
        }
      });
    }),
  );
  filteredParagraphs = combineLatest([this.renderedParagraphs, this.selectedTypes, this.selectedLabels]).pipe(
    map(([paragraphs, types, labels]) => {
      if (types.length > 0) {
        paragraphs = paragraphs.filter((paragraph) => paragraph.kind && types.includes(paragraph.kind));
      }
      if (labels.length > 0) {
        paragraphs = paragraphs.filter((paragraph) =>
          paragraph.activeClassifications.some((classification) =>
            labels.some((label) => label.labelset === classification.labelset && label.label === classification.label),
          ),
        );
      }
      return paragraphs;
    }),
  );

  questionsAnswers = this.editResourceService.currentFieldData.pipe(
    map((field) => field?.extracted?.question_answers?.question_answers.question_answer),
  );
  selectedTab: 'content' | 'questions-answers' = 'content';

  currentFieldId?: FieldId;

  messages = this.paragraphService.messages;
  conversationPage = this.paragraphService.conversationPage;
  hasMorePages = this.paragraphService.hasMorePages;
  attachments = this.messages.pipe(
    map((messages) =>
      (messages || []).reduce(
        (acc, curr) => {
          acc[curr.ident] =
            (curr.content.attachments_fields || []).length > 0 ? curr.content.attachments_fields : undefined;
          return acc;
        },
        {} as { [key: string]: MessageAttachment[] | undefined },
      ),
    ),
  );

  constructor(
    private editResource: EditResourceService,
    private paragraphService: ParagraphService,
    private editResourceService: EditResourceService,
    private sdk: SDKService,
    private cdr: ChangeDetectorRef,
    private resourceNavigation: ResourceNavigationService,
    private previewService: PreviewService,
  ) {
    this.resourceNavigation.currentRoute = this.route;
  }

  ngOnInit(): void {
    this.editResource.setCurrentView('preview');
    combineLatest([this.fieldId, this.resource])
      .pipe(
        switchMap(([fieldId, resource]) =>
          this.paragraphService
            .initParagraphs(fieldId, resource, fieldId.field_type === FIELD_TYPE.conversation ? 1 : undefined)
            .pipe(map(() => ({ fieldId, resource }))),
        ),
        takeUntil(this.unsubscribeAll),
      )
      .subscribe(({ fieldId, resource }) => {
        this.currentFieldId = fieldId;
        this.errors = getErrors(fieldId, resource).filter((error) => error.code_str !== DATA_AUGMENTATION_ERROR);
        this.dataAugmentationErrors = getErrors(fieldId, resource).filter(
          (error) => error.code_str === DATA_AUGMENTATION_ERROR,
        );
        this.selectedTab = 'content';
        this.loaded = true;
        this.cdr.markForCheck();
      });

    this.isOnResourcePage
      .pipe(
        filter((isResourcePage) => isResourcePage),
        switchMap(() => this.editResourceService.fields),
        map((fields) => fields.filter((field) => field.field_type !== FIELD_TYPE.generic)),
        filter((fields) => fields.length === 1),
        map((fields) => fields[0]),
        takeUntil(this.unsubscribeAll),
      )
      .subscribe((field) => {
        this.currentFieldId = field;
        this.cdr.markForCheck();
      });
  }

  ngOnDestroy() {
    this.unsubscribeAll.next();
    this.unsubscribeAll.complete();
    this.paragraphService.cleanup();
    const viewerElement = document.querySelector('nuclia-viewer') as any;
    if (typeof viewerElement?.$$c?.$destroy === 'function') {
      viewerElement.$$c.$destroy();
    }
  }

  openViewer() {
    this.loadingPreview = true;
    if (this.currentFieldId) {
      const fieldId = this.currentFieldId;
      this.resource
        .pipe(
          take(1),
          switchMap((resource) => this.previewService.openViewer({ ...fieldId, resourceId: resource.id })),
          filter((isPreviewing) => !!isPreviewing),
          take(1),
        )
        .subscribe(() => {
          this.loadingPreview = false;
          this.cdr.markForCheck();
        });
    }
  }

  onClickOption(type: TypeParagraph, event: MouseEvent | KeyboardEvent) {
    if ((event.target as HTMLElement).tagName === 'LI') {
      this.toggleType(type);
    }
  }

  toggleType(type: TypeParagraph) {
    this.selectedTypes.next(
      this.selectedTypes.value.includes(type)
        ? this.selectedTypes.value.filter((item) => item !== type)
        : [...this.selectedTypes.value, type],
    );
  }

  updateSelectedLabels(labels: Classification[]) {
    this.selectedLabels.next(labels);
  }

  removeLabel(label: Classification) {
    this.selectedLabels.next(
      this.selectedLabels.value.filter((item) => !(item.labelset === label.labelset && item.label === label.label)),
    );
  }

  getGeneratedFileUrl(path: string) {
    return this.sdk.currentKb.pipe(
      switchMap((kb) =>
        (kb.state === 'PUBLISHED' ? of('') : kb.getTempToken()).pipe(
          map((token) => this.sdk.nuclia.rest.getFullUrl(path + (token ? `?eph-token=${token}` : ''))),
        ),
      ),
    );
  }

  loadMoreMessages() {
    combineLatest([this.fieldId, this.resource, this.conversationPage])
      .pipe(
        take(1),
        switchMap(([fieldId, resource, conversationPage]) =>
          this.paragraphService.initParagraphs(fieldId, resource, conversationPage + 1),
        ),
      )
      .subscribe();
  }

  navigateToField(attachment: MessageAttachment) {
    const path = `../../${attachment.field_type}/${attachment.field_id}`;
    this.router.navigate([path], { relativeTo: this.route });
  }

  openWarnings() {
    return this.modalService.openModal(
      WarningModalComponent,
      new ModalConfig({ data: { errors: this.dataAugmentationErrors } }),
    );
  }
}
