import { ChangeDetectionStrategy, Component, Input, signal, ViewEncapsulation } from '@angular/core';
import {
  BoundViewModelAttributeRemovalError,
  DataSourceNotFoundError,
  DataSourceType,
  InterfaceDesignerSchemaService,
  PropertyPanel,
  SchemaEditor,
  ViewModelAttributeType,
  ViewNodeEditor,
  ViewNodePropertyValueType,
  ViewModelAttributeNotFoundError,
} from '@creatio/interface-designer';
import { CrtInput, CrtViewElement } from '@creatio-devkit/common';

import { RATING_PANEL_SELECTOR, RATING_PANEL_TYPE } from '../../design-feature.ids';

const MIN_STAR_COUNT = 1;
const MAX_STAR_COUNT = 10;

type RatingSource = 'constant' | 'dataSource';
type RatingSize = 'small' | 'medium' | 'large';

@CrtViewElement({
  selector: RATING_PANEL_SELECTOR,
  type: RATING_PANEL_TYPE,
})
@Component({
  selector: 'usr-rating-panel-internal',
  standalone: false,
  templateUrl: './rating-panel.component.html',
  styleUrls: ['./rating-panel.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RatingPanelComponent implements PropertyPanel {
  private readonly _schemaEditor: SchemaEditor = new InterfaceDesignerSchemaService().getSchemaEditor();

  private _viewNodeEditorInstance: ViewNodeEditor | null = null;

  private get _viewNodeEditor(): ViewNodeEditor {
    if (!this._viewNodeEditorInstance) {
      throw new Error('View node editor is not initialized');
    }
    return this._viewNodeEditorInstance;
  }

  protected readonly isPanelReady = signal(false);
  protected readonly validationMessage = signal('');

  protected readonly ratingSource = signal<RatingSource>('constant');
  protected readonly starCount = signal(5);
  protected readonly ratingValueConstant = signal(0);
  protected readonly dataSourceName = signal('');
  protected readonly dataSourceAttributePath = signal('');

  protected readonly size = signal<RatingSize>('medium');
  protected readonly activeColor = signal('#FFC107');
  protected readonly inactiveColor = signal('#D1D5DB');
  protected readonly visible = signal(true);
  protected readonly isReadOnly = signal(false);
  protected readonly isDisabled = signal(false);

  @Input()
  @CrtInput()
  public set viewNodeEditor(nodeEditor: ViewNodeEditor) {
    this._init(nodeEditor).catch((error: unknown) => console.error('Error initializing rating panel:', error));
  }

  private async _init(nodeEditor: ViewNodeEditor): Promise<void> {
    this._viewNodeEditorInstance = nodeEditor;
    await this._loadElementData();
    this.isPanelReady.set(true);
  }

  private async _loadElementData(): Promise<void> {
    const starCountValue = await this._viewNodeEditor.getPropertyValue('starCount');
    if (starCountValue?.type === ViewNodePropertyValueType.Constant) {
      this.starCount.set(Number(starCountValue.value) || 5);
    }

    const sizeValue = await this._viewNodeEditor.getPropertyValue('size');
    if (sizeValue?.type === ViewNodePropertyValueType.Constant) {
      this.size.set((sizeValue.value as RatingSize) ?? 'medium');
    }

    const activeColorValue = await this._viewNodeEditor.getPropertyValue('activeColor');
    if (activeColorValue?.type === ViewNodePropertyValueType.Constant) {
      this.activeColor.set((activeColorValue.value as string) ?? '#FFC107');
    }

    const inactiveColorValue = await this._viewNodeEditor.getPropertyValue('inactiveColor');
    if (inactiveColorValue?.type === ViewNodePropertyValueType.Constant) {
      this.inactiveColor.set((inactiveColorValue.value as string) ?? '#D1D5DB');
    }

    const visibleValue = await this._viewNodeEditor.getPropertyValue('visible');
    if (visibleValue?.type === ViewNodePropertyValueType.Constant) {
      this.visible.set((visibleValue.value as boolean) ?? true);
    }

    const isReadOnlyValue = await this._viewNodeEditor.getPropertyValue('isReadOnly');
    if (isReadOnlyValue?.type === ViewNodePropertyValueType.Constant) {
      this.isReadOnly.set((isReadOnlyValue.value as boolean) ?? false);
    }

    const isDisabledValue = await this._viewNodeEditor.getPropertyValue('isDisabled');
    if (isDisabledValue?.type === ViewNodePropertyValueType.Constant) {
      this.isDisabled.set((isDisabledValue.value as boolean) ?? false);
    }

    const ratingValue = await this._viewNodeEditor.getPropertyValue('ratingValue');
    if (ratingValue?.type === ViewNodePropertyValueType.Constant) {
      this.ratingSource.set('constant');
      this.ratingValueConstant.set(Number(ratingValue.value) || 0);
    } else if (ratingValue?.type === ViewNodePropertyValueType.AttributeBinding) {
      const attrEditor = await this._schemaEditor.viewModelEditor.getAttributeEditor(ratingValue.attributePath);
      if (attrEditor?.attributeType === ViewModelAttributeType.ModelBindingValue) {
        const binding = await attrEditor.getModelBinding();
        this.ratingSource.set('dataSource');
        this.dataSourceName.set(binding.dataSourceName ?? '');
        this.dataSourceAttributePath.set(binding.dataSourceAttributePath ?? '');
      }
    }
  }

  protected async onStarCountChange(event: Event): Promise<void> {
    const value = Math.trunc(Number((event.target as HTMLInputElement).value));

    if (!Number.isFinite(value) || value < MIN_STAR_COUNT || value > MAX_STAR_COUNT) {
      this.validationMessage.set(`Star count must be between ${MIN_STAR_COUNT} and ${MAX_STAR_COUNT}.`);
      return;
    }

    this.validationMessage.set('');
    this.starCount.set(value);
    await this._viewNodeEditor.setPropertyValue('starCount', { constant: value });

    if (this.ratingSource() === 'constant' && this.ratingValueConstant() > value) {
      this.ratingValueConstant.set(value);
      await this._viewNodeEditor.setPropertyValue('ratingValue', { constant: value });
    }
  }

  protected async onRatingValueConstantChange(event: Event): Promise<void> {
    const value = Math.trunc(Number((event.target as HTMLInputElement).value));
    const maxValue = this.starCount();

    if (!Number.isFinite(value) || value < 0 || value > maxValue) {
      this.validationMessage.set(`Rating value must be between 0 and ${maxValue}.`);
      return;
    }

    this.validationMessage.set('');
    this.ratingValueConstant.set(value);
    await this._viewNodeEditor.setPropertyValue('ratingValue', { constant: value });
  }

  protected async onSizeChange(event: Event): Promise<void> {
    const value = (event.target as HTMLSelectElement).value as RatingSize;
    this.size.set(value);
    await this._viewNodeEditor.setPropertyValue('size', { constant: value });
  }

  protected async onActiveColorChange(event: Event): Promise<void> {
    const value = (event.target as HTMLInputElement).value;
    this.activeColor.set(value);
    await this._viewNodeEditor.setPropertyValue('activeColor', { constant: value });
  }

  protected async onInactiveColorChange(event: Event): Promise<void> {
    const value = (event.target as HTMLInputElement).value;
    this.inactiveColor.set(value);
    await this._viewNodeEditor.setPropertyValue('inactiveColor', { constant: value });
  }

  protected async onVisibleChange(event: Event): Promise<void> {
    const value = (event.target as HTMLInputElement).checked;
    this.visible.set(value);
    await this._viewNodeEditor.setPropertyValue('visible', { constant: value });
  }

  protected async onReadOnlyChange(event: Event): Promise<void> {
    const value = (event.target as HTMLInputElement).checked;
    this.isReadOnly.set(value);
    await this._viewNodeEditor.setPropertyValue('isReadOnly', { constant: value });
  }

  protected async onDisabledChange(event: Event): Promise<void> {
    const value = (event.target as HTMLInputElement).checked;
    this.isDisabled.set(value);
    await this._viewNodeEditor.setPropertyValue('isDisabled', { constant: value });
  }

  protected async onRatingSourceChange(event: Event): Promise<void> {
    const value = (event.target as HTMLSelectElement).value as RatingSource;
    this.ratingSource.set(value);
    this.validationMessage.set('');

    if (value === 'constant') {
      await this._unbindDataSourceIfBound();
      await this._viewNodeEditor.setPropertyValue('ratingValue', { constant: this.ratingValueConstant() });
    } else {
      await this._tryApplyDataSourceBinding();
    }
  }

  protected async onDataSourceNameChange(event: Event): Promise<void> {
    this.dataSourceName.set((event.target as HTMLInputElement).value);
    await this._tryApplyDataSourceBinding();
  }

  protected async onDataSourceAttributePathChange(event: Event): Promise<void> {
    this.dataSourceAttributePath.set((event.target as HTMLInputElement).value);
    await this._tryApplyDataSourceBinding();
  }

  private _viewModelAttributeName(): string {
    return `${this._viewNodeEditor.nodeName}_Rating`;
  }

  private async _unbindDataSourceIfBound(): Promise<void> {
    const viewModelAttributeName = this._viewModelAttributeName();
    const attrEditor = await this._schemaEditor.viewModelEditor.getAttributeEditor(viewModelAttributeName);
    if (!attrEditor) {
      return;
    }

    await this._viewNodeEditor.setPropertyValue('ratingValue', { constant: null });
    if (await this._schemaEditor.viewModelEditor.canRemoveAttribute(viewModelAttributeName)) {
      await this._schemaEditor.viewModelEditor.removeAttribute(viewModelAttributeName);
    }
  }

  private async _tryApplyDataSourceBinding(): Promise<void> {
    const dsName = this.dataSourceName().trim();
    const dsAttributePath = this.dataSourceAttributePath().trim();

    if (!dsName || !dsAttributePath) {
      this.validationMessage.set('');
      return;
    }

    try {
      const dataSourceEditor = await this._schemaEditor.modelEditor.getDataSourceEditor(dsName);
      if (!dataSourceEditor) {
        this.validationMessage.set(`Data source "${dsName}" not found.`);
        return;
      }
      if (dataSourceEditor.dataSourceType !== DataSourceType.EntityDataSource) {
        this.validationMessage.set(`Data source "${dsName}" is not a supported type.`);
        return;
      }

      const viewModelAttributeName = this._viewModelAttributeName();
      const modelBinding = { dataSourceName: dsName, dataSourceAttributePath: dsAttributePath };
      const attrEditor = await this._schemaEditor.viewModelEditor.getAttributeEditor(viewModelAttributeName);

      if (!attrEditor) {
        await this._schemaEditor.viewModelEditor.createAttribute(viewModelAttributeName, { bindToModel: modelBinding });
      } else if (attrEditor.attributeType === ViewModelAttributeType.ModelBindingValue) {
        await attrEditor.bindToModel(modelBinding);
      } else {
        this.validationMessage.set(`View model attribute "${viewModelAttributeName}" cannot be reused.`);
        return;
      }

      await this._viewNodeEditor.setPropertyValue('ratingValue', { bindToAttribute: viewModelAttributeName });
      this.validationMessage.set('');
    } catch (error) {
      this.validationMessage.set(this._describeError(error));
    }
  }

  private _describeError(error: unknown): string {
    if (error instanceof DataSourceNotFoundError) {
      return `Data source "${error.dataSourceName}" not found.`;
    }
    if (error instanceof ViewModelAttributeNotFoundError) {
      return `View model attribute "${error.attributePath}" not found.`;
    }
    if (error instanceof BoundViewModelAttributeRemovalError) {
      return `View model attribute "${error.attributePath}" is in use and cannot be removed.`;
    }
    return 'Changes are not applied: an unexpected error occurred.';
  }
}
