import { Meta, Story, html } from '@open-wc/demoing-storybook';
/* eslint-disable import/no-extraneous-dependencies */
import { LionInput } from '@lion/input/index.js';
import '@lion/input-amount/lion-input-amount.js';
import '@lion/input-date/lion-input-date.js';
import '@lion/input-email/lion-input-email.js';
import '@lion/input/lion-input.js';
import {
  DefaultSuccess,
  EqualsLength,
  IsDate,
  IsEmail,
  IsNumber,
  loadDefaultFeedbackMessages,
  MaxDate,
  MaxLength,
  MaxNumber,
  MinDate,
  MinLength,
  MinMaxDate,
  MinMaxLength,
  MinMaxNumber,
  MinNumber,
  Required,
  Validator,
} from '@lion/validate';

<Meta title="Forms/Validation/Examples" parameters={{ component: 'lion-input' }}/>

## Required Validator

The required validator can be put onto every form field element and will make sure that element is not empty.
For an input that may mean that it is not an empty string while for a checkbox group it means at least one checkbox needs to be checked.

<Story name="Required Validator">
  {html`
    <lion-input .validators=${[new Required()]} label="Required"></lion-input>
  `}
</Story>

```html
<lion-input .validators="${[new Required()]}" label="Required"></lion-input>
```

## String Validators

Useful on input elements it allows to define how many characters can be entered.

<Story name="String Validators">
  {html`
    <lion-input
      .validators=${[new EqualsLength(7)]}
      .modelValue=${'not exactly'}
      label="EqualsLength"
    ></lion-input>
    <lion-input
      .validators=${[new MinLength(10)]}
      .modelValue=${'too short'}
      label="MinLength"
    ></lion-input>
    <lion-input
      .validators=${[new MaxLength(7)]}
      .modelValue=${'too long'}
      label="MaxLength"
    ></lion-input>
    <lion-input
      .validators=${[new MinMaxLength({ min: 10, max: 20 })]}
      .modelValue=${'that should be enough'}
      label="MinMaxLength"
    ></lion-input>
  `}
</Story>

```html
<lion-input
  .validators="${[new EqualsLength(7)]}"
  .modelValue="${'not exactly'}"
  label="EqualsLength"
></lion-input>
<lion-input
  .validators="${[new MinLength(10)]}"
  .modelValue="${'too short'}"
  label="MinLength"
></lion-input>
<lion-input
  .validators="${[new MaxLength(7)]}"
  .modelValue="${'too long'}"
  label="MaxLength"
></lion-input>
<lion-input
  .validators="${[new MinMaxLength({ min: 10, max: 20 })]}"
  .modelValue="${'that should be enough'}"
  label="MinMaxLength"
></lion-input>
```

## Number Validators

Number validations assume that it's modelValue is actually a number.
Therefore it may only be used on input that have an appropriate parser/formatter like the input-amount.

<Story name="Number Validators">
  {html`
  <lion-input-amount
    .validators="${[new IsNumber()]}"
    .modelValue="${'foo'}"
    label="IsNumber"
  ></lion-input-amount>
  <lion-input-amount
    .validators="${[new MinNumber(7)]}"
    .modelValue="${5}"
    label="MinNumber"
  ></lion-input-amount>
  <lion-input-amount
    .validators="${[new MaxNumber(7)]}"
    .modelValue="${9}"
    label="MaxNumber"
  ></lion-input-amount>
  <lion-input-amount
    .validators="${[new MinMaxNumber({ min: 10, max: 20 })]}"
    .modelValue="${5}"
    label="MinMaxNumber"
  ></lion-input-amount>
  `}
</Story>

```html
<lion-input-amount
  .validators="${[new IsNumber()]}"
  .modelValue="${'foo'}"
  label="IsNumber"
></lion-input-amount>
<lion-input-amount
  .validators="${[new MinNumber(7)]}"
  .modelValue="${5}"
  label="MinNumber"
></lion-input-amount>
<lion-input-amount
  .validators="${[new MaxNumber(7)]}"
  .modelValue="${9}"
  label="MaxNumber"
></lion-input-amount>
<lion-input-amount
  .validators="${[new MinMaxNumber({ min: 10, max: 20 })]}"
  .modelValue="${5}"
  label="MinMaxNumber"
></lion-input-amount>
```

## Date Validators

Date validators work with real javascript dates. Use them on input-date.

<Story name="Date Validators">
  {() => {
    const today = new Date();
    const year = today.getFullYear();
    const month = today.getMonth();
    const day = today.getDate();
    const yesterday = new Date(year, month, day - 1);
    const tomorrow = new Date(year, month, day + 1);
    return html`
      <lion-input-date
        .validators=${[new IsDate()]}
        .modelValue=${'foo'}
        label="IsDate"
      ></lion-input-date>
      <lion-input-date
        .validators=${[new MinDate(today)]}
        .modelValue=${new Date(yesterday)}
        label="MinDate"
      ></lion-input-date>
      <lion-input-date
        .validators=${[new MaxDate(today)]}
        .modelValue=${new Date(tomorrow)}
        label="MaxDate"
      ></lion-input-date>
      <lion-input-date
        .validators=${[new MinMaxDate({ min: new Date(yesterday), max: new Date(tomorrow) })]}
        .modelValue=${new Date(today)}
        label="MinMaxDate"
      ></lion-input-date>
    `;
  }}
</Story>


```js
const today = new Date();
const year = today.getFullYear();
const month = today.getMonth();
const day = today.getDate();
const yesterday = new Date(year, month, day - 1);
const tomorrow = new Date(year, month, day + 1);
```

```html
<lion-input-date
  .validators="${[new IsDate()]}"
  .modelValue="${'foo'}"
  label="IsDate"
></lion-input-date>
<lion-input-date
  .validators="${[new MinDate(today)]}"
  .modelValue="${new Date(yesterday)}"
  label="MinDate"
></lion-input-date>
<lion-input-date
  .validators="${[new MaxDate(today)]}"
  .modelValue="${new Date(tomorrow)}"
  label="MaxDate"
></lion-input-date>
<lion-input-date
  .validators="${[new MinMaxDate({ min: new Date(yesterday), max: new Date(tomorrow) })]}"
  .modelValue="${new Date(today)}"
  label="MinMaxDate"
></lion-input-date>
```

## Email Validator

<Story name="Email Validator">
  {html`
    <lion-input-email
      .validators="${[new IsEmail()]}"
      .modelValue="${'foo'}"
      label="IsEmail"
    ></lion-input-email>
  `}
</Story>

```html
<lion-input-email
  .validators="${[new IsEmail()]}"
  .modelValue="${'foo'}"
  label="IsEmail"
></lion-input-email>
```

## Validation Types

When defining your own component you can decide to allow for multiple types of validation.
By default only `error` is used however there are certainly use cases where warning or success messages make sense.

<Story name="Validation Types">
  {() => {
    try {
      class MyTypesInput extends LionInput {
        static get validationTypes() {
          return ['error', 'warning', 'info', 'success'];
        }
      }
      customElements.define('my-types-input', MyTypesInput);
    } catch (err) {
      // expected as it is a demo
    }
    return html`
      <style>
        my-types-input {
          border-left: 2px solid #fff;
        }
        my-types-input[shows-feedback-for]:not([shows-feedback-for='']) {
          border-left: 2px dotted red;
        }
        my-types-input[shows-feedback-for~='error'] input {
          outline: 2px solid red;
        }
        my-types-input[shows-feedback-for~='warning'] input {
          outline: 2px solid orange;
        }
        lion-validation-feedback[type='success'] {
          color: green;
        }
        lion-validation-feedback[type='error'] {
          color: red;
        }
        lion-validation-feedback[type='warning'] {
          color: orange;
        }
        lion-validation-feedback[type='info'] {
          color: blue;
        }
      </style>
      <my-types-input
        .validators="${[
          new Required(),
          new MinLength(7, { type: 'warning' }),
          new MaxLength(10, {
            type: 'info',
            getMessage: () => `Please, keep the length below the 10 characters.`,
          }),
          new DefaultSuccess(),
        ]}"
        .modelValue="${'exactly'}"
        label="Validation Types"
      ></my-types-input>
    `;
  }}
</Story>

```js
try {
  class MyTypesInput extends LionInput {
    static get validationTypes() {
      return ['error', 'warning', 'info', 'success'];
    }
  }
  customElements.define('my-types-input', MyTypesInput);
} catch (err) {
  // expected as it is a demo
}
```

```html
<style>
  my-types-input {
    border-left: 2px solid #fff;
  }
  my-types-input[shows-feedback-for]:not([shows-feedback-for='']) {
    border-left: 2px dotted red;
  }
  my-types-input[shows-feedback-for~='error'] input {
    outline: 2px solid red;
  }
  my-types-input[shows-feedback-for~='warning'] input {
    outline: 2px solid orange;
  }
  lion-validation-feedback[type='success'] {
    color: green;
  }
  lion-validation-feedback[type='error'] {
    color: red;
  }
  lion-validation-feedback[type='warning'] {
    color: orange;
  }
  lion-validation-feedback[type='info'] {
    color: blue;
  }
</style>
<my-types-input
  .validators="${[
    new Required(),
    new MinLength(7, { type: 'warning' }),
    new MaxLength(10, {
      type: 'info',
      getMessage: () => `Please, keep the length below the 10 characters.`,
    }),
    new DefaultSuccess(),
  ]}"
  .modelValue="${'exactly'}"
  label="Validation Types"
></my-types-input>
```

## Custom Validators

Here is an example how you can make your own validator and providing the error messages directly within.
You can even hard code localization in there if needed or you can use a localization system.

<Story name="Custom Validators">
  {() => {
    class MyValidator extends Validator {
      constructor(...args) {
        super(...args);
        this.name = 'myValidator';
      }
      execute(modelValue, param) {
        return modelValue !== param;
      }
      static getMessage({ fieldName, modelValue, params: param }) {
        if (modelValue.length >= param.length - 1 && param.startsWith(modelValue)) {
          return 'Almost there...';
        }
        return `No "${param}" found in ${fieldName}`;
      }
    }
    return html`
      <lion-input
        label="Custom validator"
        help-text="Type 'mine' please"
        .validators="${[new MyValidator('mine')]}"
        .modelValue="${'mi'}"
      ></lion-input>
    `;
    }
  }
</Story>


```js
class MyValidator extends Validator {
  constructor(...args) {
    super(...args);
    this.name = 'myValidator';
  }

  execute(modelValue, param) {
    return modelValue !== param;
  }

  static getMessage({ fieldName, modelValue, params: param }) {
    if (modelValue.length >= param.length - 1 && param.startsWith(modelValue)) {
      return 'Almost there...';
    }
    return `No "${param}" found in ${fieldName}`;
  }
}
```

```html
<lion-input
  label="Custom validator"
  help-text="Type 'mine' please"
  .validators="${[new MyValidator('mine')]}"
  .modelValue="${'mi'}"
></lion-input>
```

## Override default messages

Oftern

<Story name="Override default messages">
{html`
  <lion-input
    .validators="${[new EqualsLength(4, { getMessage: () => '4 chars please...' })]}"
    .modelValue="${'123'}"
    label="Custom message for validator instance"
  ></lion-input>
  <lion-input
    .validators="${[
      new EqualsLength(4, {
        getMessage: ({ modelValue, params: param }) => {
          const diff = modelValue.length - param;
          return `${Math.abs(diff)} too ${diff > 0 ? 'much' : 'few'}...`;
        },
      }),
    ]}"
    .modelValue="${'way too much'}"
    label="Dynamic message for validator instance"
  ></lion-input>
  `}
</Story>

```html
<lion-input
  .validators="${[new EqualsLength(4, { getMessage: () => '4 chars please...' })]}"
  .modelValue="${'123'}"
  label="Custom message for validator instance"
></lion-input>
<lion-input
  .validators="${[
    new EqualsLength(4, {
      getMessage: ({ modelValue, params: param }) => {
        const diff = modelValue.length - param;
        return `${Math.abs(diff)} too ${diff > 0 ? 'much' : 'few'}...`;
      },
    }),
  ]}"
  .modelValue="${'way too much'}"
  label="Dynamic message for validator instance"
></lion-input>
```

## Override fieldName

<Story name="Override fieldName">
{html`
  <lion-input
    .validators="${[new EqualsLength(4, { fieldName: 'custom fieldName' })]}"
    .modelValue="${'123'}"
    label="Custom fieldName for 1 validator"
  ></lion-input>
  <lion-input
    .validators="${[new Required(), new EqualsLength(4)]}"
    .fieldName="${'custom fieldName'}"
    .modelValue="${'123'}"
    label="Custom fieldName for all validators"
  ></lion-input>
`}
</Story>

```html
<lion-input
  .validators="${[new EqualsLength(4, { fieldName: 'custom fieldName' })]}"
  .modelValue="${'123'}"
  label="Custom fieldName for 1 validator"
></lion-input>
<lion-input
  .validators="${[new Required(), new EqualsLength(4)]}"
  .fieldName="${'custom fieldName'}"
  .modelValue="${'123'}"
  label="Custom fieldName for all validators"
></lion-input>
```

## Asynchronous validation

<Story name="Asynchronous validation">
{() => {
    function pause(ms = 0) {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve();
        }, ms);
      });
    }
    class AsyncValidator extends Validator {
      constructor(...args) {
        super(...args);
        this.name = 'asyncValidator';
        this.async = true;
      }
      async execute() {
        console.log('async pending...');
        await pause(2000);
        console.log('async done...');
        return true;
      }
      static getMessage({ modelValue }) {
        return `validated for modelValue: ${modelValue}...`;
      }
    }
    return html`
      <style>
        lion-input[is-pending] {
          opacity: 0.5;
        }
      </style>
      <lion-input
        label="Async validation"
        .validators="${[new AsyncValidator()]}"
        .modelValue="${'123'}"
      ></lion-input>
    `}}
</Story>

```js
function pause(ms = 0) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
}
class AsyncValidator extends Validator {
  constructor(...args) {
    super(...args);
    this.name = 'asyncValidator';
    this.async = true;
  }
  async execute() {
    console.log('async pending...');
    await pause(2000);
    console.log('async done...');
    return true;
  }
  static getMessage({ modelValue }) {
    return `validated for modelValue: ${modelValue}...`;
  }
}
```

## Dynamic parameter change

<Story name="Dynamic parameter change">
{() => {
    const beginDate = new Date('09/09/1990');
    const minDateValidatorRef = new MinDate(beginDate, {
      message: 'Fill in a date after your birth date',
    });
    return html`
      <lion-input-date
        label="Your birth date"
        help-text="Adjust this date to retrigger validation of the input below..."
        .modelValue="${beginDate}"
        @model-value-changed="${({ target: { modelValue, errorState } }) => {
          if (!errorState) {
            // Since graduation date is usually not before birth date
            minDateValidatorRef.param = modelValue;
          }
        }}"
      ></lion-input-date>
      <lion-input-date
        label="Your graduation date"
        .modelValue="${new Date('09/09/1989')}"
        .validators="${[minDateValidatorRef]}"
      ></lion-input-date>
    `}}
</Story>

```js
const beginDate = new Date('09/09/1990');
const minDateValidatorRef = new MinDate(beginDate, {
  message: 'Fill in a date after your birth date',
});
```

```html
<lion-input-date
  label="Your birth date"
  help-text="Adjust this date to retrigger validation of the input below..."
  .modelValue="${beginDate}"
  @model-value-changed="${({ target: { modelValue, errorState } }) => {
    if (!errorState) {
      // Since graduation date is usually not before birth date
      minDateValidatorRef.param = modelValue;
    }
  }}"
></lion-input-date>
<lion-input-date
  label="Your graduation date"
  .modelValue="${new Date('09/09/1989')}"
  .validators="${[minDateValidatorRef]}"
></lion-input-date>
```
