import {
  CompilerOptions,
  parse,
  transform,
  ElementNode,
  NodeTypes
} from '../../src'
import { transformElement } from '../../src/transforms/transformElement'
import { transformOn } from '../../src/transforms/vOn'
import { transformBind } from '../../src/transforms/vBind'
import { transformExpression } from '../../src/transforms/transformExpression'
import { RENDER_SLOT } from '../../src/runtimeConstants'
import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'

function parseWithSlots(template: string, options: CompilerOptions = {}) {
  const ast = parse(template)
  transform(ast, {
    nodeTransforms: [
      ...(options.prefixIdentifiers ? [transformExpression] : []),
      transformSlotOutlet,
      transformElement
    ],
    directiveTransforms: {
      on: transformOn,
      bind: transformBind
    },
    ...options
  })
  return ast
}

describe('compiler: transform <slot> outlets', () => {
  test('default slot outlet', () => {
    const ast = parseWithSlots(`<slot/>`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [`$slots.default`]
    })
  })

  test('statically named slot outlet', () => {
    const ast = parseWithSlots(`<slot name="foo" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [`$slots.foo`]
    })
  })

  test('statically named slot outlet w/ name that needs quotes', () => {
    const ast = parseWithSlots(`<slot name="foo-bar" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [`$slots["foo-bar"]`]
    })
  })

  test('dynamically named slot outlet', () => {
    const ast = parseWithSlots(`<slot :name="foo" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        {
          type: NodeTypes.COMPOUND_EXPRESSION,
          children: [
            `$slots[`,
            {
              type: NodeTypes.SIMPLE_EXPRESSION,
              content: `foo`,
              isStatic: false
            },
            `]`
          ]
        }
      ]
    })
  })

  test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
    const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
      prefixIdentifiers: true
    })
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: RENDER_SLOT,
      arguments: [
        {
          type: NodeTypes.COMPOUND_EXPRESSION,
          children: [
            `_ctx.$slots[`,
            {
              type: NodeTypes.SIMPLE_EXPRESSION,
              content: `_ctx.foo`,
              isStatic: false
            },
            ` + `,
            {
              type: NodeTypes.SIMPLE_EXPRESSION,
              content: `_ctx.bar`,
              isStatic: false
            },
            `]`
          ]
        }
      ]
    })
  })

  test('default slot outlet with props', () => {
    const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.default`,
        {
          type: NodeTypes.JS_OBJECT_EXPRESSION,
          properties: [
            {
              key: {
                content: `foo`,
                isStatic: true
              },
              value: {
                content: `bar`,
                isStatic: true
              }
            },
            {
              key: {
                content: `baz`,
                isStatic: true
              },
              value: {
                content: `qux`,
                isStatic: false
              }
            }
          ]
        }
      ]
    })
  })

  test('statically named slot outlet with props', () => {
    const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.foo`,
        {
          type: NodeTypes.JS_OBJECT_EXPRESSION,
          // props should not include name
          properties: [
            {
              key: {
                content: `foo`,
                isStatic: true
              },
              value: {
                content: `bar`,
                isStatic: true
              }
            },
            {
              key: {
                content: `baz`,
                isStatic: true
              },
              value: {
                content: `qux`,
                isStatic: false
              }
            }
          ]
        }
      ]
    })
  })

  test('dynamically named slot outlet with props', () => {
    const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        {
          type: NodeTypes.COMPOUND_EXPRESSION,
          children: [`$slots[`, { content: `foo` }, `]`]
        },
        {
          type: NodeTypes.JS_OBJECT_EXPRESSION,
          // props should not include name
          properties: [
            {
              key: {
                content: `foo`,
                isStatic: true
              },
              value: {
                content: `bar`,
                isStatic: true
              }
            },
            {
              key: {
                content: `baz`,
                isStatic: true
              },
              value: {
                content: `qux`,
                isStatic: false
              }
            }
          ]
        }
      ]
    })
  })

  test('default slot outlet with fallback', () => {
    const ast = parseWithSlots(`<slot><div/></slot>`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.default`,
        `{}`,
        [
          {
            type: NodeTypes.ELEMENT,
            tag: `div`
          }
        ]
      ]
    })
  })

  test('named slot outlet with fallback', () => {
    const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.foo`,
        `{}`,
        [
          {
            type: NodeTypes.ELEMENT,
            tag: `div`
          }
        ]
      ]
    })
  })

  test('default slot outlet with props & fallback', () => {
    const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.default`,
        {
          type: NodeTypes.JS_OBJECT_EXPRESSION,
          properties: [
            {
              key: {
                content: `foo`,
                isStatic: true
              },
              value: {
                content: `bar`,
                isStatic: false
              }
            }
          ]
        },
        [
          {
            type: NodeTypes.ELEMENT,
            tag: `div`
          }
        ]
      ]
    })
  })

  test('named slot outlet with props & fallback', () => {
    const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: `_${RENDER_SLOT}`,
      arguments: [
        `$slots.foo`,
        {
          type: NodeTypes.JS_OBJECT_EXPRESSION,
          properties: [
            {
              key: {
                content: `foo`,
                isStatic: true
              },
              value: {
                content: `bar`,
                isStatic: false
              }
            }
          ]
        },
        [
          {
            type: NodeTypes.ELEMENT,
            tag: `div`
          }
        ]
      ]
    })
  })
})
