import { Page } from 'playwright';
import { AbstractLocator, AbstractWidget } from '../AbstractWidget';
import { WidgetFindOptions } from '../types';
import { expect } from '@/core/fixtures';
import { widgetStep } from '@/core/utils';
import { getLocator } from '@/core/locators';

export type FormInputFindOptions = WidgetFindOptions & {
  label?: string;
  fieldName?: string;
};

export abstract class AbstractFormInputWidget<
  ValueType = unknown,
> extends AbstractWidget {
  type = '';

  private errorValidationSelector = '.danger.invalid-feedback';
  private warningValidationSelector = '.warning.invalid-feedback';
  private noteSelector = '[data-role="form-field-note"]';

  constructor(
    protected findOptions: FormInputFindOptions,
    protected readonly page: Page,
    protected readonly parent?: AbstractWidget,
    protected readonly parentLocator?: AbstractLocator
  ) {
    super(findOptions, page, parent, parentLocator);
  }

  /**
   *
   * @description change input value
   */
  abstract change(val?: ValueType): void;

  /**
   *
   * @description expect input value to be equal to provided value
   */
  abstract hasValue(val: ValueType): void;

  /**
   *
   * @description expect input value to be NOT equal to provided value
   */
  abstract hasNotValue(val: ValueType): void;

  /**
   * @description expects input to be empty
   */

  abstract isEmpty(): void;

  find() {
    const { label, fieldName } = this.findOptions;
    const selectors: string[] = [];
    if (fieldName) selectors.push(`[data-fieldname="${fieldName}"]`);

    let input = this.makeLocator(selectors);
    if (label)
      input = input.filter({
        has: this.page.locator('label', { hasText: label }),
      });

    return input;
  }

  private validationMessage(
    text: string,
    type: 'Error' | 'Warning' = 'Error',
    exact: boolean = false
  ) {
    return expect(
      this.l
        .locator(
          type === 'Error'
            ? this.errorValidationSelector
            : this.warningValidationSelector,
          {
            hasText: exact ? new RegExp(`^${text}$`) : text,
          }
        )
        .first()
    );
  }

  @widgetStep
  async toHaveValidationMessage(
    text: string,
    type: 'Error' | 'Warning' = 'Error',
    exact: boolean = false
  ) {
    await this.validationMessage(text, type, exact).toBeVisible();
  }
  @widgetStep
  async notToHaveValidationMessage(
    text: string,
    type: 'Error' | 'Warning' = 'Error',
    exact: boolean = false
  ) {
    await this.validationMessage(text, type, exact).not.toBeVisible();
  }

  @widgetStep
  async toHaveErrorValidation(text: string, exact: boolean = false) {
    await this.toHaveValidationMessage(text, 'Error', exact);
  }
  @widgetStep
  async toHaveWarningValidation(text: string, exact: boolean = false) {
    await this.toHaveValidationMessage(text, 'Warning', exact);
  }

  @widgetStep
  async notToHaveErrorValidation(text: string, exact: boolean = false) {
    await this.notToHaveValidationMessage(text, 'Error', exact);
  }

  @widgetStep
  async notToHaveWarningValidation(text: string, exact: boolean = false) {
    await this.notToHaveValidationMessage(text, 'Warning', exact);
  }

  private note(text: string, exact: boolean = false) {
    return expect(
      this.l
        .locator(this.noteSelector, {
          hasText: exact ? new RegExp(`^${text}$`) : text,
        })
        .first()
    );
  }

  @widgetStep
  async toHaveNote(text: string, exact: boolean = false) {
    await this.note(text, exact).toBeVisible();
  }

  @widgetStep
  async notToHaveNote(text: string, exact: boolean = false) {
    await this.note(text, exact).not.toBeVisible();
  }

  async saved() {
    await expect(
      this.l.locator('[data-icon="floppy-disk"]').first()
    ).toBeVisible();
  }

  async isDisabled() {
    return await expect(this.l.locator('input')).toBeDisabled();
  }
}

export class AbstractDropdownLikeInputWidget extends AbstractFormInputWidget {
  protected getDropdownToggleLocator() {
    return getLocator('input.select.dropdownToggle', this.l);
  }

  get menuLocator() {
    return getLocator('input.select.menu', this.page);
  }

  @widgetStep
  async toggle() {
    await this.getDropdownToggleLocator().click();
    return this;
  }

  @widgetStep
  async close() {
    try {
      await this.page.keyboard.down('Escape');
      await this.page.waitForTimeout(50);
    } catch {
      //it's ok, 99.9% it's closed already
    }
    return this;
  }

  change() {
    throw new Error('Method not implemented.');
  }

  @widgetStep
  async hasValue(val: string) {
    await expect(
      this.l.locator(`[data-role="single-value"]`, { hasText: val })
    ).toBeVisible();
  }

  @widgetStep
  async hasNotValue(val: string) {
    await expect(
      this.l.locator(`[data-role="single-value"]`, { hasText: val })
    ).not.toBeVisible();
  }

  @widgetStep
  async isEmpty() {
    await expect(this.l.locator(`[data-empty="true"]`)).toBeVisible();
  }
}
