import {
  booleanAttribute,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  EventEmitter,
  inject,
  Input,
  OnDestroy,
  OnInit,
  Output,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  DropdownButtonComponent,
  ExpandableTextareaComponent,
  InfoCardComponent,
  SisModalService,
  StickyFooterComponent,
  TwoColumnsConfigurationItemComponent,
} from '@nuclia/sistema';
import {
  ModalConfig,
  OptionModel,
  PaButtonModule,
  PaDropdownModule,
  PaExpanderModule,
  PaIconModule,
  PaTableModule,
  PaTextFieldModule,
  PaTogglesModule,
  PaTooltipModule,
} from '@guillotinaweb/pastanaga-angular';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { LabelModule, LabelsService, ParametersTableComponent, SDKService } from '@flaps/core';
import { TranslateModule } from '@ngx-translate/core';
import {
  Classification,
  FIELD_TYPE,
  LearningConfigurations,
  LLMConfig,
  longToShortFieldType,
  GenerativeProviders,
  TaskFullDefinition,
  TaskName,
  TaskTrigger,
  ReasoningConfig,
} from '@nuclia/core';
import { BehaviorSubject, filter, forkJoin, map, Subject, switchMap } from 'rxjs';
import { delay, take, takeUntil } from 'rxjs/operators';
import { TasksAutomationService } from '../tasks-automation.service';
import { ModelSelectorComponent, ReasoningConfigComponent, UserKeysComponent, UserKeysForm } from '../../ai-models';
import { DataAugmentationTaskOnGoing, getOperationFromTaskName, hasFilters } from '../tasks-automation.models';
import { RouterModule } from '@angular/router';
import { FilterExpressionModalComponent } from '../../search-widget/search-configuration/filter-expression-modal';

const DEFAULT_CHEAP_LLM = 'gemini-2.5-flash-lite';

const DEFAULT_FILTER_EXPRESSION = `{
  "field": {
    "and": [
      {
        "not": {
          "prop": "field",
          "type": "generic"
        }
      }
    ]
  }
}`;

export interface TaskFormCommonConfig {
  name: string;
  filter: {
    contains: string[];
    contains_operator?: 0 | 1;
    field_types: string[];
    labels?: string[];
    labels_operator?: 0 | 1;
    apply_to_agent_generated_fields?: boolean;
  };
  filter_expression_json?: string;
  llm: LLMConfig;
  webhook?: TaskTrigger;
}

@Component({
  selector: 'app-task-form',
  imports: [
    CommonModule,
    DropdownButtonComponent,
    ExpandableTextareaComponent,
    InfoCardComponent,
    LabelModule,
    ModelSelectorComponent,
    PaDropdownModule,
    PaExpanderModule,
    PaIconModule,
    ParametersTableComponent,
    PaTextFieldModule,
    PaTogglesModule,
    PaTooltipModule,
    ReactiveFormsModule,
    TwoColumnsConfigurationItemComponent,
    TranslateModule,
    PaTableModule,
    PaButtonModule,
    ReasoningConfigComponent,
    RouterModule,
    StickyFooterComponent,
    UserKeysComponent,
  ],
  templateUrl: './task-form.component.html',
  styleUrl: './task-form.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskFormComponent implements OnInit, OnDestroy {
  private labelService = inject(LabelsService);
  private sdk = inject(SDKService);
  private cdr = inject(ChangeDetectorRef);
  private tasksAutomation = inject(TasksAutomationService);
  private modalService = inject(SisModalService);

  private unsubscribeAll = new Subject<void>();

  // Task type
  @Input() type: TaskName = 'labeler';
  // Text displayed in the info card below Trigger label on the right column
  @Input() triggerDescription = '';
  // Note displayed on the footer when task is applied automatically
  @Input() footerNoteAutomation = '';
  // Note displayed on the footer when task is applied once
  @Input() footerNoteOneTime = '';
  // Flag indicating if the form inside ng-content is valid
  @Input({ transform: booleanAttribute }) validFormInside = false;
  // Flag indicating if the model selection is hidden
  @Input({ transform: booleanAttribute }) set modelsHidden(value: boolean) {
    this._modelsHidden = value;
    this.form.controls.llm.controls.model.clearValidators();
  }
  get modelsHidden() {
    return this._modelsHidden;
  }
  private _modelsHidden = false;
  // Task whose data is displayed in the form
  @Input() set task(value: DataAugmentationTaskOnGoing | undefined | null) {
    if (value) {
      this._task = value;
      this.initForm(value);
    }
  }
  get task() {
    return this._task;
  }
  private _task?: DataAugmentationTaskOnGoing;

  @Output() cancel = new EventEmitter<void>();
  @Output() save = new EventEmitter<TaskFormCommonConfig>();

  form = new FormGroup({
    name: new FormControl<string>('', { nonNullable: true, validators: [Validators.required] }),
    filter: new FormGroup({
      contains: new FormControl<string>('', { nonNullable: true }),
      contains_operator: new FormControl<boolean>(false),
      labels_operator: new FormControl<boolean>(false),
      apply_to_agent_generated_fields: new FormControl<boolean>(false),
    }),
    filter_expression_json: new FormControl<string>(DEFAULT_FILTER_EXPRESSION, { nonNullable: true }),
    llm: new FormGroup({
      model: new FormControl<string>('', { nonNullable: true, validators: [Validators.required] }),
    }),
    webhook: new FormGroup({
      url: new FormControl<string>('', { nonNullable: true }),
    }),
  });
  userKeysForm?: UserKeysForm;
  reasoningConfig?: ReasoningConfig;
  headers: { key: string; value: string }[] = [];
  parameters: { key: string; value: string }[] = [];
  tasksRoute = this.tasksAutomation.tasksRoute;
  formInitialized = new BehaviorSubject(false);
  selectedFilters = new BehaviorSubject<string[]>([]);
  hasFilters = false;
  recommendedLLM = '';
  currentLLM = '';

  get generativeModel() {
    return this.learningConfigurations?.['generative_model'].options?.find(
      (option) => option.value === this.form.controls.llm.controls.model.value,
    );
  }
  get filterExpression() {
    return this.form.controls.filter_expression_json.value;
  }

  resourceCount?: number;

  fieldTypeFilters: OptionModel[] = [FIELD_TYPE.file, FIELD_TYPE.link, FIELD_TYPE.text, FIELD_TYPE.conversation].map(
    (t) => new OptionModel({ id: longToShortFieldType(t), value: longToShortFieldType(t), label: t }),
  );
  get allFieldTypesSelected() {
    return this.fieldTypeFilters.every((option) => option.selected);
  }
  get fieldTypeSelectionCount(): number {
    return this.fieldTypeFilters.filter((option) => option.selected).length;
  }
  get fieldTypesTotalCount(): number {
    return this.fieldTypeFilters.length;
  }
  get selectedFieldTypes() {
    return this.fieldTypeFilters.filter((option) => option.selected).map((option) => option.value);
  }

  hasLabelSets = this.labelService.hasResourceLabelSets;
  resourceLabelSets = this.labelService.resourceLabelSets;
  labelSelection: Classification[] = [];
  get labelFilters() {
    return this.labelSelection.map((label) => `${label.labelset}/${label.label}`);
  }
  get labelSelectionCount(): number {
    return this.labelFilters.length;
  }
  taskDefinition?: TaskFullDefinition;
  learningConfigurations?: LearningConfigurations;
  generativeProviders: GenerativeProviders = {};
  unsupportedProviders = ['none'];

  get properties() {
    return this.taskDefinition?.validation.properties;
  }
  get filterProperties() {
    return this.properties?.['filter']?.['anyOf']?.[0]?.properties;
  }
  can_apply_to_agent_generated_fields = false;

  ngOnInit() {
    this.tasksAutomation.taskDefinitions
      .pipe(
        map((tasks) => tasks.find((task) => task.name === this.type)),
        filter((task) => !!task),
        take(1),
      )
      .subscribe((task) => {
        this.taskDefinition = task;
        this.can_apply_to_agent_generated_fields = task.name === 'labeler' || task.name === 'llm-graph';
        this.cdr.markForCheck();
      });

    this.sdk.currentKb
      .pipe(
        take(1),
        switchMap((kb) => forkJoin([kb.getLearningSchema(), kb.getGenerativeProviders()])),
      )
      .subscribe(([schema, providers]) => {
        this.learningConfigurations = schema;
        this.generativeProviders = Object.fromEntries(
          Object.entries(providers).filter(([key]) => !this.unsupportedProviders.includes(key)),
        );
        const models = schema?.['generative_model'].options || [];
        const hasCheapLLM = models.some((option) => option.value === DEFAULT_CHEAP_LLM);
        const hasDefaultLLM = models.some((option) => option.value === schema?.['generative_model']?.default);
        if (hasCheapLLM) {
          this.recommendedLLM = DEFAULT_CHEAP_LLM;
        } else if (hasDefaultLLM) {
          this.recommendedLLM = schema?.['generative_model']?.default ?? '';
        }
        if (!this.form.controls.llm.controls.model.value) {
          this.form.controls.llm.controls.model.setValue(this.recommendedLLM);
          this.currentLLM = this.recommendedLLM;
        }
        this.cdr.markForCheck();
      });

    /*
    combineLatest([this.form.valueChanges, this.selectedFilters])
      .pipe(
        filter(([data]) => data.applyTaskTo === 'EXISTING'),
        debounceTime(300),
        switchMap(([data, filters]) =>
          this.sdk.currentKb.pipe(switchMap((kb) => kb.catalog(data.filter?.contains || '', { filters }))),
        ),
        takeUntil(this.unsubscribeAll),
      )
      .subscribe({
        next: (results) => {
          if (results.type !== 'error') {
            this.resourceCount = results.fulltext?.total;
            this.cdr.markForCheck();
          }
        },
        error: () => this.toaster.error('tasks-automation.errors.counting-resources'),
      });
    */
  }

  ngOnDestroy() {
    this.unsubscribeAll.next();
    this.unsubscribeAll.complete();
  }

  initForm(task: DataAugmentationTaskOnGoing) {
    const triggers =
      this.task?.parameters.operations?.[0]?.[getOperationFromTaskName(this.task.task.name) || 'ask']?.triggers?.[0];
    this.form.patchValue({
      ...task.parameters,
      filter: {
        ...task.parameters.filter,
        contains: task.parameters.filter.contains?.join(', ') || '',
        contains_operator: task.parameters.filter.contains_operator === 1,
        labels_operator: task.parameters.filter.labels_operator === 1,
      },
      webhook: { url: triggers?.url || '' },
    });
    this.currentLLM = task.parameters.llm?.model || '';
    this.hasFilters = hasFilters(task.parameters);
    this.fieldTypeFilters.forEach((option) => {
      option.selected = (task.parameters.filter.field_types || []).includes(option.id);
    });
    this.labelSelection = (task.parameters.filter.labels || []).map((label) => ({
      labelset: label.split('/')[0],
      label: label.split('/')[1],
    }));
    this.reasoningConfig = task.parameters.llm.reasoning_config;
    if (triggers) {
      this.headers = Object.entries(triggers.headers || {}).map(([key, value]) => ({ key, value }));
      this.parameters = Object.entries(triggers.params || {}).map(([key, value]) => ({ key, value }));
    }
    this.formInitialized.next(true);
    this.cdr.markForCheck();
  }

  initUserKeysForm(form: UserKeysForm) {
    this.userKeysForm = form;
    this.formInitialized
      .pipe(
        filter((value) => value),
        take(1),
        delay(100), // Wait until the form is updated
      )
      .subscribe(() => {
        if (this.generativeModel?.user_key) {
          const userkeys = this.task?.parameters.llm.keys?.[this.generativeModel.user_key];
          this.userKeysForm?.patchValue({
            enabled: !!userkeys,
            user_keys: userkeys,
          });
        }
      });
    // Change detection is needed when the child component changes the form
    this.userKeysForm.valueChanges.pipe(takeUntil(this.unsubscribeAll)).subscribe(() => {
      this.cdr.detectChanges();
    });
  }

  toggleAll(filter: 'types' | 'fieldTypes', event: MouseEvent | KeyboardEvent) {
    const tagName = (event.target as HTMLElement).tagName;
    if (tagName === 'LI' || tagName === 'INPUT') {
      switch (filter) {
        case 'fieldTypes':
          this.fieldTypeFilters = this.fieldTypeFilters.map(
            (option) => new OptionModel({ ...option, selected: !this.allFieldTypesSelected }),
          );
          break;
      }
      this.onToggleFilter();
    }
  }

  onSelectFilter(option: OptionModel, event: MouseEvent | KeyboardEvent) {
    if ((event.target as HTMLElement).tagName === 'LI') {
      option.selected = !option.selected;
      this.onToggleFilter();
    }
  }

  updateFiltersWithLabels() {
    this.onToggleFilter();
  }

  onToggleFilter() {
    this.selectedFilters.next(this.selectedFieldTypes.concat(this.labelFilters));
  }

  onModelSelected(value: string) {
    if (!value) {
      // User clicked the "edit" button to clear the selection and open the search box.
      // Don't update currentLLM — it should stay as the last confirmed selection
      // so cancel can correctly revert to it.
      return;
    }
    if (!this.recommendedLLM || value === this.recommendedLLM || value === this.currentLLM) {
      this.currentLLM = value;
      return;
    }
    this.modalService
      .openConfirm({
        title: 'tasks-automation.form.llm-selection.confirm-dialog.title',
        description: 'tasks-automation.form.llm-selection.confirm-dialog.description',
      })
      .onClose.pipe(take(1))
      .subscribe((confirmed) => {
        if (confirmed) {
          this.currentLLM = value;
          return;
        }
        this.form.controls.llm.controls.model.setValue(this.recommendedLLM, { emitEvent: false });
      });
  }

  onSave() {
    const rawValue = this.form.getRawValue();
    const userKeys = this.userKeysForm?.getRawValue();
    this.save.emit({
      ...rawValue,
      webhook: rawValue.webhook.url.trim()
        ? {
            ...rawValue.webhook,
            headers: this.headers
              .filter((header) => header.key.trim() && header.value.trim())
              .reduce((acc, curr) => ({ ...acc, [curr.key]: curr.value }), {}),
            params: this.parameters
              .filter((header) => header.key.trim() && header.value.trim())
              .reduce((acc, curr) => ({ ...acc, [curr.key]: curr.value }), {}),
          }
        : undefined,
      filter: {
        contains: rawValue.filter.contains ? rawValue.filter.contains.split(',').map((s) => s.trim()) : [],
        contains_operator: rawValue.filter.contains_operator ? 1 : 0,
        field_types: this.selectedFieldTypes,
        labels: this.labelFilters,
        labels_operator: rawValue.filter.labels_operator ? 1 : 0,
        apply_to_agent_generated_fields: !!rawValue.filter.apply_to_agent_generated_fields,
      },
      llm: {
        ...rawValue.llm,
        keys:
          this.generativeModel?.user_key && userKeys?.enabled
            ? { [this.generativeModel.user_key]: userKeys.user_keys }
            : {},
        reasoning_config: this.reasoningConfig,
      },
    });
  }

  openFilterExpression() {
    this.modalService
      .openModal(
        FilterExpressionModalComponent,
        new ModalConfig({
          data: {
            filterExpression: this.filterExpression,
            dataAugmentation: true,
            help: 'tasks-automation.form.filter.filter-expression.help',
          },
        }),
      )
      .onClose.pipe(filter((filters) => !!filters))
      .subscribe((filters: string) => this.form.controls.filter_expression_json.patchValue(filters));
  }
}
