import { describe, expect, it, vi } from 'vitest'
import type { Plugin } from 'vite'
import type { Component } from '@nuxt/schema'
import type { UnpluginOptions } from 'unplugin'
import { IslandsTransformPlugin } from '../src/components/plugins/islands-transform'
import { normalizeLineEndings } from './utils'

const getComponents = () => [{
  filePath: '/root/hello.server.vue',
  mode: 'server',
  pascalName: 'HelloWorld',
  island: true,
  kebabName: 'hello-world',
  chunkName: 'components/hello-world',
  export: 'default',
  shortPath: '',
  prefetch: false,
  preload: false,
}] as Component[]

const pluginWebpack = IslandsTransformPlugin({
  getComponents,
  selectiveClient: true,
}).raw({}, { framework: 'webpack', webpack: { compiler: {} as any } })

const viteTransform = async (source: string, id: string, selectiveClient = false) => {
  const vitePlugin = IslandsTransformPlugin({
    getComponents,
    selectiveClient,
  }).raw({}, { framework: 'vite' }) as Plugin

  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
  const result = await (vitePlugin.transform! as Function)(source, id)
  return typeof result === 'string' ? result : result?.code
}

const webpackTransform = async (source: string, id: string) => {
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
  const result = await ((pluginWebpack as UnpluginOptions).transform! as Function)(source, id)
  return typeof result === 'string' ? result : result?.code
}

describe('islandTransform - server and island components', () => {
  describe('slots', () => {
    it('expect slot transform to match inline snapshot', async () => {
      const result = await viteTransform(`<template>
      <div>
        <slot />

        <slot name="named" :some-data="someData" />
        <slot
          name="other"
          :some-data="someData"
        />
      </div>
      </template>
      <script setup lang="ts">
      const someData = 'some data'

      </script>`
      , 'hello.server.vue')

      expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
        "<template>
              <div>
                <NuxtTeleportSsrSlot name="default" :props="undefined"><slot /></NuxtTeleportSsrSlot>

                <NuxtTeleportSsrSlot name="named" :props="[{ [\`some-data\`]: someData }]"><slot name="named" :some-data="someData" /></NuxtTeleportSsrSlot>
                <NuxtTeleportSsrSlot name="other" :props="[{ [\`some-data\`]: someData }]"><slot
                  name="other"
                  :some-data="someData"
                /></NuxtTeleportSsrSlot>
              </div>
              </template>
              <script setup lang="ts">
        import { mergeProps as __mergeProps } from 'vue'
        import { vforToArray as __vforToArray } from '#app/components/utils'
        import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
        import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
              const someData = 'some data'

              </script>"
      `)
    })

    it('generates bindings when props are needed to be merged', async () => {
      const result = await viteTransform(`<script setup lang="ts">
withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
  things: () => [],
  somethingElse: "yay",
});
</script>

<template>
  <template v-for="thing in things">
    <slot name="thing" v-bind="thing" />
  </template>
</template>
`, 'hello.server.vue')

      expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
        "<script setup lang="ts">
        import { mergeProps as __mergeProps } from 'vue'
        import { vforToArray as __vforToArray } from '#app/components/utils'
        import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
        import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
        withDefaults(defineProps<{ things?: any[]; somethingElse?: string }>(), {
          things: () => [],
          somethingElse: "yay",
        });
        </script>

        <template>
          <template v-for="thing in things">
            <NuxtTeleportSsrSlot name="thing" :props="[__mergeProps(thing, {  })]"><slot name="thing" v-bind="thing" /></NuxtTeleportSsrSlot>
          </template>
        </template>
        "
      `)
    })

    it('expect slot fallback transform to match inline snapshot', async () => {
      const result = await viteTransform(`<template>
      <div>
        <slot :some-data="someData">
          <div>fallback</div>
        </slot>
      </div>
      </template>
      <script setup lang="ts">
      const someData = 'some data'

      </script>`
      , 'hello.server.vue')

      expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
        "<template>
              <div>
                <NuxtTeleportSsrSlot name="default" :props="[{ [\`some-data\`]: someData }]"><slot :some-data="someData"/><template #fallback>
                  <div>fallback</div>
                </template></NuxtTeleportSsrSlot>
              </div>
              </template>
              <script setup lang="ts">
        import { mergeProps as __mergeProps } from 'vue'
        import { vforToArray as __vforToArray } from '#app/components/utils'
        import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
        import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
              const someData = 'some data'

              </script>"
      `)
    })

    it('expect slot transform with fallback and no name to match inline snapshot #23209', async () => {
      const result = await viteTransform(`<template>
      <div>
        <UCard>
          <template #header>
            <h3>Partial Hydration Example - Server - {{ count }}</h3>
          </template>
          <template #default>
            <p>message: {{ message }}</p>
            <p>Below is the slot I want to be hydrated on the client</p>
            <div>
              <slot>
                This is the default content of the slot, I should not see this after
                the client loading has completed.
              </slot>
            </div>
            <p>Above is the slot I want to be hydrated on the client</p>
          </template>
        </UCard>
      </div>
    </template>

    <script setup lang="ts">
    export interface Props {
      count?: number;
    }
    const props = withDefaults(defineProps<Props>(), { count: 0 });

    const message = "Hello World";
    </script>
    `
      , 'hello.server.vue')

      expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
        "<template>
              <div>
                <UCard>
                  <template #header>
                    <h3>Partial Hydration Example - Server - {{ count }}</h3>
                  </template>
                  <template #default>
                    <p>message: {{ message }}</p>
                    <p>Below is the slot I want to be hydrated on the client</p>
                    <div>
                      <NuxtTeleportSsrSlot name="default" :props="undefined"><slot/><template #fallback>
                        This is the default content of the slot, I should not see this after
                        the client loading has completed.
                      </template></NuxtTeleportSsrSlot>
                    </div>
                    <p>Above is the slot I want to be hydrated on the client</p>
                  </template>
                </UCard>
              </div>
            </template>

            <script setup lang="ts">
        import { mergeProps as __mergeProps } from 'vue'
        import { vforToArray as __vforToArray } from '#app/components/utils'
        import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
        import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
            export interface Props {
              count?: number;
            }
            const props = withDefaults(defineProps<Props>(), { count: 0 });

            const message = "Hello World";
            </script>
            "
      `)
    })

    it('expect v-if/v-else/v-else-if to be set in teleport component wrapper', async () => {
      const result = await viteTransform(`<script setup lang="ts">
      const foo = true;
      </script>
      <template>
      <slot v-if="foo" />
      <slot v-else-if="test" />
      <slot v-else />
      </template>
      `, 'WithVif.vue', true)

      expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
        "<script setup lang="ts">
        import { mergeProps as __mergeProps } from 'vue'
        import { vforToArray as __vforToArray } from '#app/components/utils'
        import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
        import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
              const foo = true;
              </script>
              <template>
              <NuxtTeleportSsrSlot v-if="foo" name="default" :props="undefined"><slot  /></NuxtTeleportSsrSlot>
              <NuxtTeleportSsrSlot v-else-if="test" name="default" :props="undefined"><slot  /></NuxtTeleportSsrSlot>
              <NuxtTeleportSsrSlot v-else name="default" :props="undefined"><slot  /></NuxtTeleportSsrSlot>
              </template>
              "
      `)
    })
  })

  describe('nuxt-client', () => {
    describe('vite', () => {
      it('test transform with vite', async () => {
        const result = await viteTransform(`<template>
        <div>
          <HelloWorld />
          <HelloWorld nuxt-client />
        </div>
      </template>

      <script setup lang="ts">
      import HelloWorld from './HelloWorld.vue'
      </script>
      `, 'hello.server.vue', true)

        expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
          "<template>
                  <div>
                    <HelloWorld />
                    <NuxtTeleportIslandComponent to="HelloWorld-CyH3UXLuYA" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
                  </div>
                </template>

                <script setup lang="ts">
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
                import HelloWorld from './HelloWorld.vue'
                </script>
                "
        `)
      })

      it('test dynamic nuxt-client', async () => {
        const result = await viteTransform(`<template>
        <div>
          <HelloWorld />
          <HelloWorld :nuxt-client="nuxtClient" />
        </div>
      </template>

      <script setup lang="ts">
      import HelloWorld from './HelloWorld.vue'

      const nuxtClient = false
      </script>
      `, 'hello.server.vue', true)

        expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
          "<template>
                  <div>
                    <HelloWorld />
                    <NuxtTeleportIslandComponent to="HelloWorld-eo0XycWCUV" :nuxt-client="nuxtClient"><HelloWorld /></NuxtTeleportIslandComponent>
                  </div>
                </template>

                <script setup lang="ts">
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
                import HelloWorld from './HelloWorld.vue'

                const nuxtClient = false
                </script>
                "
        `)
      })

      it('should not transform if disabled', async () => {
        const result = await viteTransform(`<template>
        <div>
          <HelloWorld />
          <HelloWorld :nuxt-client="nuxtClient" />
        </div>
      </template>

      <script setup lang="ts">
      import HelloWorld from './HelloWorld.vue'

      const nuxtClient = false
      </script>
      `, 'hello.server.vue', false)

        expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
          "<template>
                  <div>
                    <HelloWorld />
                    <HelloWorld :nuxt-client="nuxtClient" />
                  </div>
                </template>

                <script setup lang="ts">
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
                import HelloWorld from './HelloWorld.vue'

                const nuxtClient = false
                </script>
                "
        `)
      })

      it('should add import if there is no scripts in the SFC', async () => {
        const result = await viteTransform(`<template>
        <div>
          <HelloWorld />
          <HelloWorld nuxt-client />
        </div>
      </template>

      `, 'hello.server.vue', true)

        expect(result).toMatchInlineSnapshot(`
          "<script setup>
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
                  <div>
                    <HelloWorld />
                    <NuxtTeleportIslandComponent to="HelloWorld-CyH3UXLuYA" :nuxt-client="true"><HelloWorld /></NuxtTeleportIslandComponent>
                  </div>
                </template>

                "
        `)
        expect(result).toContain('import NuxtTeleportIslandComponent from \'#app/components/nuxt-teleport-island-component\'')
      })

      it('should move v-if to the wrapper component', async () => {
        const result = await viteTransform(`<template>
        <div>
        <HelloWorld v-if="false" nuxt-client />
        <HelloWorld v-else-if="true" nuxt-client />
        <HelloWorld v-else nuxt-client />
        </div>
      </template>
      `, 'hello.server.vue', true)

        expect(result).toMatchInlineSnapshot(`
          "<script setup>
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'</script><template>
                  <div>
                  <NuxtTeleportIslandComponent v-if="false" to="HelloWorld-D9uaHyzL7X" :nuxt-client="true"><HelloWorld  /></NuxtTeleportIslandComponent>
                  <NuxtTeleportIslandComponent v-else-if="true" to="HelloWorld-o4RZMtArnE" :nuxt-client="true"><HelloWorld  /></NuxtTeleportIslandComponent>
                  <NuxtTeleportIslandComponent v-else to="HelloWorld-m1IbXHdd8O" :nuxt-client="true"><HelloWorld  /></NuxtTeleportIslandComponent>
                  </div>
                </template>
                "
        `)
      })
    })

    describe('webpack', () => {
      it('test transform with webpack', async () => {
        const spyOnWarn = vi.spyOn(console, 'warn')
        const result = await webpackTransform(`<template>
        <div>
          <!-- should not be wrapped by NuxtTeleportIslandComponent -->
          <HelloWorld />

          <!-- should be not wrapped by NuxtTeleportIslandComponent for now -->
          <HelloWorld nuxt-client />
        </div>
      </template>

      <script setup lang="ts">
      import HelloWorld from './HelloWorld.vue'

      const someData = 'some data'
      </script>
      `, 'hello.server.vue')
        expect(normalizeLineEndings(result)).toMatchInlineSnapshot(`
          "<template>
                  <div>
                    <!-- should not be wrapped by NuxtTeleportIslandComponent -->
                    <HelloWorld />

                    <!-- should be not wrapped by NuxtTeleportIslandComponent for now -->
                    <HelloWorld nuxt-client />
                  </div>
                </template>

                <script setup lang="ts">
          import { mergeProps as __mergeProps } from 'vue'
          import { vforToArray as __vforToArray } from '#app/components/utils'
          import NuxtTeleportIslandComponent from '#app/components/nuxt-teleport-island-component'
          import NuxtTeleportSsrSlot from '#app/components/nuxt-teleport-island-slot'
                import HelloWorld from './HelloWorld.vue'

                const someData = 'some data'
                </script>
                "
        `)

        expect(spyOnWarn).toHaveBeenCalledWith('The `nuxt-client` attribute and client components within islands are only supported with Vite. file: hello.server.vue')
      })
    })
  })
})
