/**
 * @fileoverview Enforces props default values to be valid.
 * @author Armano
 */
'use strict'

const rule = require('../../../lib/rules/require-valid-default-prop')
const {
  getTypeScriptFixtureTestOptions
} = require('../../test-utils/typescript')
const RuleTester = require('../../eslint-compat').RuleTester

const languageOptions = {
  ecmaVersion: 2020,
  sourceType: 'module',
  parserOptions: {
    ecmaFeatures: { jsx: true }
  }
}

function errorMessage(type) {
  return [
    {
      message: `Type of the default value for 'foo' prop must be a ${type}.`,
      line: 5
    }
  ]
}

function errorMessageForFunction(type) {
  return [
    {
      message: `Type of the default value for 'foo' prop must be a ${type}.`,
      line: 6
    }
  ]
}

const ruleTester = new RuleTester()
ruleTester.run('require-valid-default-prop', rule, {
  valid: [
    {
      filename: 'test.vue',
      code: `export default {
        ...foo,
        props: { ...foo }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: { foo: null }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: ['foo']
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Object, Number],
            default: 10
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `Vue.component('example', {
        props: {
          foo: null,
          foo: Number,
          foo: [String, Number],
          foo: { },
          foo: { type: String },
          foo: { type: Number, default: VAR_BAR },
          foo: { type: Number, default: 100 },
          foo: { type: Number, default: Number.MAX_VALUE },
          foo: { type: Number, default: Foo.BAR },
          foo: { type: {}, default: '' },
          foo: { type: [String, Number], default: '' },
          foo: { type: [String, Number], default: 0 },
          foo: { type: String, default: '' },
          foo: { type: String, default: \`\` },
          foo: { type: Boolean, default: false },
          foo: { type: Object, default: () => { } },
          foo: { type: Array, default () { } },
          foo: { type: String, default () { } },
          foo: { type: Number, default () { } },
          foo: { type: Boolean, default () { } },
          foo: { type: Symbol, default () { } },
          foo: { type: Array, default () { } },
          foo: { type: Symbol, default: Symbol('a') },
          foo: { type: String, default: \`Foo\` },
          foo: { type: Foo, default: Foo('a') },
          foo: { type: String, default: \`Foo\` },
          foo: { type: BigInt, default: 1n },
          foo: { type: String, default: null },
          foo: { type: String, default () { return Foo } },
          foo: { type: Number, default () { return Foo } },
          foo: { type: Object, default () { return Foo } },
        }
      })`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `
        export default (Vue as VueConstructor<Vue>).extend({
          props: {
            foo: {
              type: [Object, Number],
              default: 10
            } as PropOptions<object>
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      }
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Number],
            default() {
              return 10
            }
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Function, Number],
            default() {
              return 's'
            }
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Number],
            default: () => 10
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Function, Number],
            default: () => 's'
          }
        }
      }`,
      languageOptions
    },

    // sparse array
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [,Object, Number],
            default: 10
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: Number?.()
          }
        }
      }`,
      languageOptions
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Array as PropType<string[]>,
              default: () => []
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      }
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Object as PropType<{ [key: number]: number }>,
              default: () => {}
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      }
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Function as PropType<() => number>,
              default: () => 10
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      }
    },
    {
      // https://github.com/vuejs/eslint-plugin-vue/issues/1853
      filename: 'test.vue',
      code: `<script setup lang="ts">
      export interface SomePropInterface {
        someProp?: false | string;
        str?: 'foo' | 'bar';
        num?: 1 | 2;
      }

      withDefaults(defineProps<SomePropInterface>(), {
        someProp: false,
        str: 'foo',
        num: 1
      });
      </script>`,
      languageOptions: {
        parser: require('vue-eslint-parser'),
        ecmaVersion: 6,
        sourceType: 'module',
        parserOptions: {
          parser: require.resolve('@typescript-eslint/parser')
        }
      }
    },
    {
      code: `
      <script setup lang="ts">
      import {Props2 as Props} from './test01'
      withDefaults(defineProps<Props>(), {
        a: 's',
        b: 42,
        c: true,
        d: false,
        e: 's',
        f: () => 42,
        g: ()=>({ foo: 'foo' }),
        h: ()=>(['foo', 'bar']),
        i: ()=>(['foo', 'bar']),
      })
      </script>`,
      ...getTypeScriptFixtureTestOptions()
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = 'abc' } = defineProps({
          foo: {
            type: String,
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      }
    },
    {
      filename: 'test.vue',
      code: `
      <script setup lang="ts">
        const { foo = [] } = defineProps({
          foo: {
            type: Array,
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      }
    }
  ],

  invalid: [
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Number, String],
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number or string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Number, Object],
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number or function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: false
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: []
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: 2
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: []
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Boolean,
            default: ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('boolean')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Boolean,
            default: 5
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('boolean')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Boolean,
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('boolean')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Boolean,
            default: []
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('boolean')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: 55
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: false
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: []
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Array,
            default: ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Array,
            default: 55
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Array,
            default: false
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Array,
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Array,
            default: []
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [Object, Number],
            default: {}
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function or number')
    },
    {
      filename: 'test.vue',
      code: `export default (Vue as VueConstructor<Vue>).extend({
        props: {
          foo: {
            type: [Object, Number],
            default: {}
          } as PropOptions<object>
        }
      });`,

      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      },
      errors: errorMessage('function or number')
    },

    {
      filename: 'test.vue',
      code: `export default {
        props: {
          'foo': {
            type: Object,
            default: ''
          },
          ['bar']: {
            type: Object,
            default: ''
          },
          [baz]: {
            type: Object,
            default: ''
          }
        }
      }`,
      languageOptions,
      errors: [
        {
          message: `Type of the default value for 'foo' prop must be a function.`,
          line: 5
        },
        {
          message: `Type of the default value for 'bar' prop must be a function.`,
          line: 9
        },
        {
          message: `Type of the default value for '[baz]' prop must be a function.`,
          line: 13
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: 1n
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default() {
              return ''
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default() {
              return ''
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('object')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default() {
              return 123
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: () => {
              return ''
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: () => {
              return ''
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('object')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: () => {
              return 123
            }
          }
        }
      }`,
      languageOptions,
      errors: errorMessageForFunction('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Number,
            default: () => ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('number')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Object,
            default: () => ''
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('object')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: () => 123
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: Function,
            default: 1
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: [String, Boolean],
            default() {
              switch (kind) {
                case 1: return 1
                case 2: return '' // OK
                case 3: return {}
                case 4: return Foo // ignore?
                case 5: return () => {}
                case 6: return false // OK
              }

              function foo () {
                return 1 // ignore?
              }
            }
          }
        }
      }`,
      languageOptions,
      errors: [
        {
          message:
            "Type of the default value for 'foo' prop must be a string or boolean.",
          line: 7
        },
        {
          message:
            "Type of the default value for 'foo' prop must be a string or boolean.",
          line: 9
        },
        {
          message:
            "Type of the default value for 'foo' prop must be a string or boolean.",
          line: 11
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `export default {
        props: {
          foo: {
            type: String,
            default: Number?.()
          }
        }
      }`,
      languageOptions,
      errors: errorMessage('string')
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Array as PropType<string[]>,
              default: []
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      },
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Object as PropType<{ [key: number]: number }>,
              default: {}
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      },
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `export default Vue.extend({
          props: {
            foo: {
              type: Function as PropType<() => number>,
              default: 10
            }
          }
        });
      `,
      languageOptions: {
        parser: require('@typescript-eslint/parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      },
      errors: errorMessage('function')
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        defineProps({
          foo: {
            type: String,
            default: () => 123
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser'),
        ecmaVersion: 6,
        sourceType: 'module'
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a string.",
          line: 6
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        withDefaults(defineProps<{foo:string}>(),{
          foo: () => 123
        })
      </script>
      `,
      languageOptions: {
        ecmaVersion: 6,
        sourceType: 'module',
        parser: require('vue-eslint-parser'),
        parserOptions: {
          parser: require.resolve('@typescript-eslint/parser')
        }
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a string.",
          line: 4
        }
      ]
    },
    {
      code: `
      <script setup lang="ts">
      import {Props2 as Props} from './test01'
      withDefaults(defineProps<Props>(), {
        a: 42,
        b: 's',
        c: {},
        d: [],
        e: [42],
        f: {},
        g: { foo: 'foo' },
        h: ['foo', 'bar'],
        i: ['foo', 'bar'],
      })
      </script>`,
      errors: [
        {
          message: "Type of the default value for 'a' prop must be a string.",
          line: 5
        },
        {
          message: "Type of the default value for 'b' prop must be a number.",
          line: 6
        },
        {
          message: "Type of the default value for 'c' prop must be a boolean.",
          line: 7
        },
        {
          message: "Type of the default value for 'd' prop must be a boolean.",
          line: 8
        },
        {
          message:
            "Type of the default value for 'e' prop must be a string or number.",
          line: 9
        },
        {
          message: "Type of the default value for 'f' prop must be a function.",
          line: 10
        },
        {
          message: "Type of the default value for 'g' prop must be a function.",
          line: 11
        },
        {
          message: "Type of the default value for 'h' prop must be a function.",
          line: 12
        },
        {
          message: "Type of the default value for 'i' prop must be a function.",
          line: 13
        }
      ],
      ...getTypeScriptFixtureTestOptions()
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = 123 } = defineProps({
          foo: String
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a string.",
          line: 3
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = 123 } = defineProps({
          foo: {
            type: String,
            default: 123
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a string.",
          line: 3
        },
        {
          message: "Type of the default value for 'foo' prop must be a string.",
          line: 6
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = [] } = defineProps({
          foo: {
            type: Number,
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a number.",
          line: 3
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = 42 } = defineProps({
          foo: {
            type: Array,
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a array.",
          line: 3
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = [] } = defineProps({
          foo: {
            type: Array,
            default: () => {
              return 42
            }
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a array.",
          line: 7
        }
      ]
    },
    {
      filename: 'test.vue',
      code: `
      <script setup>
        const { foo = (()=>[]) } = defineProps({
          foo: {
            type: Array,
          }
        })
      </script>
      `,
      languageOptions: {
        parser: require('vue-eslint-parser')
      },
      errors: [
        {
          message: "Type of the default value for 'foo' prop must be a array.",
          line: 3
        }
      ]
    }
  ]
})
