import { Project, SyntaxKind } from 'ts-morph'
import { beforeEach, describe, expect, it } from 'vitest'

import {
  buildTailwindThemeColorsFromCssVars,
  nestSpreadElements,
  nestSpreadProperties,
  transformTailwindConfig,
  unnestSpreadProperties,
  unnsetSpreadElements,
} from '../../../src/utils/updaters/update-tailwind-config'

const SHARED_CONFIG = {
  $schema: 'https://shadcn-vue.com/schema.json',
  style: 'new-york',
  rsc: true,
  tsx: true,
  tailwind: {
    config: 'tailwind.config.ts',
    css: 'app/globals.css',
    baseColor: 'slate',
    cssVariables: true,
  },
  aliases: {
    components: '@/components',
    utils: '@/lib/utils',
  },
  resolvedPaths: {
    cwd: '.',
    tailwindConfig: 'tailwind.config.ts',
    tailwindCss: 'app/globals.css',
    components: './components',
    utils: './lib/utils',
    ui: './components/ui',
  },
}

describe('transformTailwindConfig -> darkMode property', () => {
  it('should add darkMode property if not in config', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()

    expect(
      await transformTailwindConfig(
        `/** @type {import('tailwindcss').Config} */

export default {
	content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
	theme: {
		extend: {},
	},
	plugins: [],
}
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()

    expect(
      await transformTailwindConfig(
        `/** @type {import('tailwindcss').Config} */
const foo = {
  bar: 'baz',
}

export default {
	content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
	theme: {
		extend: {},
	},
	plugins: [],
}
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should append class to darkMode property if existing array', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ["selector"],
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should preserve quote kind', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ['selector', '[data-mode="dark"]'],
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should convert string to array and add class if darkMode is string', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: "selector",
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should work with multiple darkMode selectors', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ['variant', [
    '@media (prefers-color-scheme: dark) { &:not(.light *) }',
    '&:is(.dark *)',
  ]],
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should not add darkMode property if already in config', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  darkMode: ['class'],
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
}
export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()

    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

  const config: Config = {
  darkMode: ['class', 'selector'],
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [],
  }
  export default config
  `,
        {
          properties: [
            {
              name: 'darkMode',
              value: 'class',
            },
          ],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })
})

describe('transformTailwindConfig -> plugin', () => {
  it('should add plugin if not in config', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
}
export default config
  `,
        {
          plugins: ['require("tailwindcss-animate")'],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should append plugin to existing array', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [require("@tailwindcss/typography")],
}
export default config
  `,
        {
          plugins: ['require("tailwindcss-animate")'],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should not add plugin if already in config', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      backgroundImage: {
        "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
        "gradient-conic":
          "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
      },
    },
  },
  plugins: [require("@tailwindcss/typography"), require("tailwindcss-animate")],
}
export default config
  `,
        {
          plugins: ['require(\'tailwindcss-animate\')'],
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })
})

describe('transformTailwindConfig -> theme', () => {
  it('should add theme if not in config', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

  const config: Config = {
    content: [
      "./pages/**/*.{js,ts,jsx,tsx,mdx}",
      "./components/**/*.{js,ts,jsx,tsx,mdx}",
      "./app/**/*.{js,ts,jsx,tsx,mdx}",
    ],
  }
  export default config
    `,
        {
          theme: {
            extend: {
              colors: {
                background: 'hsl(var(--background))',
                foreground: 'hsl(var(--foreground))',
                primary: {
                  DEFAULT: 'hsl(var(--primary))',
                  foreground: 'hsl(var(--primary-foreground))',
                },
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should merge existing theme', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: [
          "ui-sans-serif",
          "sans-serif",
        ],
      },
      colors: {
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              colors: {
                primary: {
                  DEFAULT: 'hsl(var(--primary))',
                  foreground: 'hsl(var(--primary-foreground))',
                },
                card: {
                  DEFAULT: 'hsl(var(--card))',
                  foreground: 'hsl(var(--card-foreground))',
                },
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should keep spread assignments', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      colors: {
        ...defaultColors,
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              colors: {
                primary: {
                  DEFAULT: 'hsl(var(--primary))',
                  foreground: 'hsl(var(--primary-foreground))',
                },
                card: {
                  DEFAULT: 'hsl(var(--card))',
                  foreground: 'hsl(var(--card-foreground))',
                },
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should handle multiple properties', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-geist-sans)", ...fontFamily.sans],
        mono: ["var(--font-mono)", ...fontFamily.mono],
      },
      colors: {
        ...defaultColors,
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
      },
      boxShadow: {
        ...defaultBoxShadow,
        "3xl": "0 35px 60px -15px rgba(0, 0, 0, 0.3)",
      },
      borderRadius: {
        "3xl": "2rem",
      },
      animation: {
        ...defaultAnimation,
        "spin-slow": "spin 3s linear infinite",
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              fontFamily: {
                heading: ['var(--font-geist-sans)'],
              },
              colors: {
                border: 'hsl(var(--border))',
                input: 'hsl(var(--input))',
                ring: 'hsl(var(--ring))',
                primary: {
                  DEFAULT: 'hsl(var(--primary))',
                  foreground: 'hsl(var(--primary-foreground))',
                },
                card: {
                  DEFAULT: 'hsl(var(--card))',
                  foreground: 'hsl(var(--card-foreground))',
                },
              },
              borderRadius: {
                lg: 'var(--radius)',
                md: 'calc(var(--radius) - 2px)',
                sm: 'calc(var(--radius) - 4px)',
              },
              animation: {
                'accordion-down': 'accordion-down 0.2s ease-out',
                'accordion-up': 'accordion-up 0.2s ease-out',
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should not make any updates running on already updated config', async () => {
    const input = `import type { Config } from 'tailwindcss'

const config: Config = {
content: [
  "./pages/**/*.{js,ts,jsx,tsx,mdx}",
  "./components/**/*.{js,ts,jsx,tsx,mdx}",
  "./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
  extend: {
    fontFamily: {
      sans: ["var(--font-geist-sans)", ...fontFamily.sans],
      mono: ["var(--font-mono)", ...fontFamily.mono],
    },
    colors: {
      ...defaultColors,
      background: "hsl(var(--background))",
      foreground: "hsl(var(--foreground))",
    },
    boxShadow: {
      ...defaultBoxShadow,
      "3xl": "0 35px 60px -15px rgba(0, 0, 0, 0.3)",
    },
    borderRadius: {
      "3xl": "2rem",
    },
    animation: {
      ...defaultAnimation,
      "spin-slow": "spin 3s linear infinite",
    },
  },
},
}
export default config
`

    const tailwindConfig = {
      theme: {
        extend: {
          fontFamily: {
            heading: ['var(--font-geist-sans)'],
          },
          colors: {
            border: 'hsl(var(--border))',
            input: 'hsl(var(--input))',
            ring: 'hsl(var(--ring))',
            primary: {
              DEFAULT: 'hsl(var(--primary))',
              foreground: 'hsl(var(--primary-foreground))',
            },
            card: {
              DEFAULT: 'hsl(var(--card))',
              foreground: 'hsl(var(--card-foreground))',
            },
          },
          borderRadius: {
            lg: 'var(--radius)',
            md: 'calc(var(--radius) - 2px)',
            sm: 'calc(var(--radius) - 4px)',
          },
          animation: {
            'accordion-down': 'accordion-down 0.2s ease-out',
            'accordion-up': 'accordion-up 0.2s ease-out',
          },
        },
      },
    }

    const output1 = await transformTailwindConfig(input, tailwindConfig, {
      config: SHARED_CONFIG,
    })

    const output2 = await transformTailwindConfig(output1, tailwindConfig, {
      config: SHARED_CONFIG,
    })

    const output3 = await transformTailwindConfig(output2, tailwindConfig, {
      config: SHARED_CONFIG,
    })

    expect(output3).toBe(output1)
    expect(output3).toBe(output2)
  })

  it('should keep quotes in strings', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      fontFamily: {
          sans: ['Figtree', ...defaultTheme.fontFamily.sans],
      },
      colors: {
        ...defaultColors,
        background: "hsl(var(--background))",
        foreground: "hsl(var(--foreground))",
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              colors: {
                primary: {
                  DEFAULT: 'hsl(var(--primary))',
                  foreground: 'hsl(var(--primary-foreground))',
                },
                card: {
                  DEFAULT: 'hsl(var(--card))',
                  foreground: 'hsl(var(--card-foreground))',
                },
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should keep arrays when formatted on multilines', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      fontFamily: {
        sans: [
          'Figtree',
          ...defaultTheme.fontFamily.sans
        ],
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              fontFamily: {
                mono: ['Foo'],
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should handle objects nested in arrays', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {
      fontSize: {
        xs: ['0.75rem', { lineHeight: '1rem' }],
        sm: [
          '0.875rem',
          {
            lineHeight: '1.25rem',
          },
        ],
      },
    },
  },
}
export default config
  `,
        {
          theme: {
            extend: {
              fontSize: {
                xl: [
                  'clamp(1.5rem, 1.04vi + 1.17rem, 2rem)',
                  {
                    lineHeight: '1.2',
                    letterSpacing: '-0.02em',
                    fontWeight: '600',
                  },
                ],
              },
            },
          },
        },
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })

  it('should preserve boolean values', async () => {
    expect(
      await transformTailwindConfig(
        `import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    container: {
      center: true
    }
  },
}
export default config
  `,
        {},
        {
          config: SHARED_CONFIG,
        },
      ),
    ).toMatchSnapshot()
  })
})

describe('nestSpreadProperties', () => {
  let project: Project

  beforeEach(() => {
    project = new Project({ useInMemoryFileSystem: true })
  })

  function testTransformation(input: string, expected: string) {
    const sourceFile = project.createSourceFile(
      'test.ts',
      `const config = ${input};`,
    )
    const configObject = sourceFile.getFirstDescendantByKind(
      SyntaxKind.ObjectLiteralExpression,
    )
    if (!configObject)
      throw new Error('Config object not found')

    nestSpreadProperties(configObject)

    const result = configObject.getText()
    expect(result.replace(/\s+/g, '')).toBe(expected.replace(/\s+/g, ''))
  }

  it('should nest spread properties', () => {
    testTransformation(
      `{ theme: { ...foo, bar: { ...baz, one: "two" }, other: { a: "b", ...c } } }`,
      `{ theme: { "___foo": "...foo", bar: { "___baz": "...baz", one: "two" }, other: { a: "b", "___c": "...c" } } }`,
    )
  })

  it('should handle mixed property assignments', () => {
    testTransformation(
      `{ ...foo, a: 1, b() {}, ...bar, c: { ...baz } }`,
      `{ "___foo": "...foo", a: 1, b() {}, "___bar": "...bar", c: { "___baz": "...baz" } }`,
    )
  })

  it('should handle objects with only spread properties', () => {
    testTransformation(
      `{ ...foo, ...bar, ...baz }`,
      `{ "___foo": "...foo", "___bar": "...bar", "___baz": "...baz" }`,
    )
  })

  it('should handle property name conflicts', () => {
    testTransformation(`{ foo: 1, ...foo }`, `{ foo: 1, "___foo": "...foo" }`)
  })

  it('should handle shorthand property names', () => {
    testTransformation(`{ a, ...foo, b }`, `{ a, "___foo": "...foo", b }`)
  })

  it('should handle computed property names', () => {
    testTransformation(
      `{ ["computed"]: 1, ...foo }`,
      `{ ["computed"]: 1, "___foo": "...foo" }`,
    )
  })

  it('should handle spreads in arrays', () => {
    testTransformation(
      `{ foo: [{ ...bar }] }`,
      `{ foo: [{ "___bar": "...bar" }] }`,
    )
  })

  it('should handle deep nesting in arrays', () => {
    testTransformation(
      `{ foo: [{ baz: { ...other.baz }, ...bar }] }`,
      `{ foo: [{ baz: { "___other.baz": "...other.baz" }, "___bar": "...bar" }] }`,
    )
  })
})

describe('nestSpreadElements', () => {
  let project: Project

  beforeEach(() => {
    project = new Project({ useInMemoryFileSystem: true })
  })

  function testTransformation(input: string, expected: string) {
    const sourceFile = project.createSourceFile(
      'test.ts',
      `const config = ${input};`,
    )
    const configObject = sourceFile.getFirstDescendantByKind(
      SyntaxKind.ArrayLiteralExpression,
    )
    if (!configObject)
      throw new Error('Config object not found')

    nestSpreadElements(configObject)

    const result = configObject.getText()
    expect(result.replace(/\s+/g, '')).toBe(expected.replace(/\s+/g, ''))
  }

  it('should spread elements', () => {
    testTransformation(
      `[...bar]`,
      `["...bar"]`,
    )
  })

  it('should handle mixed element types', () => {
    testTransformation(
      `['foo', 2, true, ...bar, "baz"]`,
      `['foo', 2, true, "...bar", "baz"]`,
    )
  })

  it('should handle arrays with only spread elements', () => {
    testTransformation(
      `[...foo, ...foo.bar, ...baz]`,
      `["...foo", "...foo.bar", "...baz"]`,
    )
  })

  it('should handle nested arrays with spreads', () => {
    testTransformation(
      `[...foo, [...bar]]`,
      `["...foo", ["...bar"]]`,
    )
  })

  it('should handle nested arrays within objects', () => {
    testTransformation(
      `[{ foo: [...foo] }]`,
      `[{ foo: ["...foo"] }]`,
    )
  })

  it('should handle deeply nested arrays within spread objects', () => {
    testTransformation(
      `[{ foo: [...foo, { bar: ['bar', ...bar ]}] }]`,
      `[{ foo: ["...foo", { bar: ['bar', "...bar" ]}] }]`,
    )
  })

  it('should handle optional paths in spread', () => {
    testTransformation(
      `[{ foo: [...foo?.bar] }]`,
      `[{ foo: ["...foo?.bar"] }]`,
    )
  })

  it('should handle computed property paths within spread', () => {
    testTransformation(
      `[{ foo: [...foo["bar"]] }]`,
      `[{ foo: ["...foo["bar"]"] }]`,
    )
  })

  it('should handle indexed paths in spread', () => {
    testTransformation(
      `[{ foo: [...foo[0]] }]`,
      `[{ foo: ["...foo[0]"] }]`,
    )
  })
})

describe('unnestSpreadProperties', () => {
  let project: Project

  beforeEach(() => {
    project = new Project({ useInMemoryFileSystem: true })
  })

  function testTransformation(input: string, expected: string) {
    const sourceFile = project.createSourceFile(
      'test.ts',
      `const config = ${input};`,
    )
    const configObject = sourceFile.getFirstDescendantByKind(
      SyntaxKind.ObjectLiteralExpression,
    )
    if (!configObject)
      throw new Error('Config object not found')

    unnestSpreadProperties(configObject)

    const result = configObject.getText()
    expect(result.replace(/\s+/g, '')).toBe(expected.replace(/\s+/g, ''))
  }

  it('should nest spread properties', () => {
    testTransformation(
      `{ theme: { ___foo: "...foo", bar: { ___baz: "...baz", one: "two" }, other: { a: "b", ___c: "...c" } } }`,
      `{ theme: { ...foo, bar: { ...baz, one: "two" }, other: { a: "b", ...c } } }`,
    )
  })

  it('should handle mixed property assignments', () => {
    testTransformation(
      `{ ___foo: "...foo", a: 1, b() {}, ___bar: "...bar", c: { ___baz: "...baz" } }`,
      `{ ...foo, a: 1, b() {}, ...bar, c: { ...baz } }`,
    )
  })

  it('should handle objects with only spread properties', () => {
    testTransformation(
      `{ ___foo: "...foo", ___bar: "...bar", ___baz: "...baz" }`,
      `{ ...foo, ...bar, ...baz }`,
    )
  })

  it('should handle property name conflicts', () => {
    testTransformation(`{ foo: 1, ___foo: "...foo" }`, `{ foo: 1, ...foo }`)
  })

  it('should handle shorthand property names', () => {
    testTransformation(`{ a, ___foo: "...foo", b }`, `{ a, ...foo, b }`)
  })

  it('should handle computed property names', () => {
    testTransformation(
      `{ ["computed"]: 1, "___foo": "...foo" }`,
      `{ ["computed"]: 1, ...foo }`,
    )
  })

  it('should handle spread objects within arrays', () => {
    testTransformation(
      `{ ["computed"]: 1, foo: [{ "___foo": "...foo" }] }`,
      `{ ["computed"]: 1, foo: [{...foo}] }`,
    )
  })

  it('should handle deeply nested spread objects within an array', () => {
    testTransformation(
      `{ ["computed"]: 1, foo: [{ "___foo": "...foo", bar: { baz: 'baz', "___foo.bar": "...foo.bar" } }] }`,
      `{ ["computed"]: 1, foo: [{...foo, bar: { baz: 'baz', ...foo.bar } }] }`,
    )
  })
})

describe('unnestSpreadElements', () => {
  let project: Project

  beforeEach(() => {
    project = new Project({ useInMemoryFileSystem: true })
  })

  function testTransformation(input: string, expected: string) {
    const sourceFile = project.createSourceFile(
      'test.ts',
      `const config = ${input};`,
    )
    const configObject = sourceFile.getFirstDescendantByKind(
      SyntaxKind.ArrayLiteralExpression,
    )
    if (!configObject)
      throw new Error('Config object not found')

    unnsetSpreadElements(configObject)

    const result = configObject.getText()
    expect(result.replace(/\s+/g, '')).toBe(expected.replace(/\s+/g, ''))
  }

  it('should spread elements', () => {
    testTransformation(
      `["...bar"]`,
      `[...bar]`,
    )
  })

  it('should handle mixed element types', () => {
    testTransformation(
      `['foo', 2, true, "...bar", "baz"]`,
      `['foo', 2, true, ...bar, "baz"]`,
    )
  })

  it('should handle arrays with only spread elements', () => {
    testTransformation(
      `["...foo", "...foo.bar", "...baz"]`,
      `[...foo, ...foo.bar, ...baz]`,
    )
  })

  it('should handle nested arrays with spreads', () => {
    testTransformation(
      `["...foo", ["...bar"]]`,
      `[...foo, [...bar]]`,
    )
  })

  it('should handle nested arrays within objects', () => {
    testTransformation(
      `[{ foo: ["...foo"] }]`,
      `[{ foo: [...foo] }]`,
    )
  })

  it('should handle deeply nested arrays within spread objects', () => {
    testTransformation(
      `[{ foo: ["...foo", { bar: ['bar', "...bar" ]}] }]`,
      `[{ foo: [...foo, { bar: ['bar', ...bar ]}] }]`,
    )
  })

  it('should handle optional paths in spread', () => {
    testTransformation(
      `[{ foo: ["...foo?.bar"] }]`,
      `[{ foo: [...foo?.bar] }]`,

    )
  })

  it('should handle computed property paths (\') within spread', () => {
    testTransformation(
      `[{ foo: ["...foo['bar']"] }]`,
      `[{ foo: [...foo['bar']] }]`,
    )
  })

  it('should handle computed property paths (") within spread', () => {
    testTransformation(
      `[{ foo: ['...foo["bar"]'] }]`,
      `[{ foo: [...foo["bar"]] }]`,
    )
  })

  it('should handle indexed paths in spread', () => {
    testTransformation(
      `[{ foo: ["...foo[0]"] }]`,
      `[{ foo: [...foo[0]] }]`,
    )
  })
})

describe('buildTailwindThemeColorsFromCssVars', () => {
  it('should inline color names', () => {
    expect(
      buildTailwindThemeColorsFromCssVars({
        'primary': 'blue',
        'primary-light': 'skyblue',
        'primary-dark': 'navy',
        'secondary': 'green',
        'accent': 'orange',
        'accent-hover': 'darkorange',
        'accent-active': 'orangered',
      }),
    ).toEqual({
      primary: {
        DEFAULT: 'hsl(var(--primary))',
        light: 'hsl(var(--primary-light))',
        dark: 'hsl(var(--primary-dark))',
      },
      secondary: 'hsl(var(--secondary))',
      accent: {
        DEFAULT: 'hsl(var(--accent))',
        hover: 'hsl(var(--accent-hover))',
        active: 'hsl(var(--accent-active))',
      },
    })
  })

  it('should not add a DEFAULT if not present', () => {
    expect(
      buildTailwindThemeColorsFromCssVars({
        'primary-light': 'skyblue',
        'primary-dark': 'navy',
        'secondary': 'green',
        'accent': 'orange',
        'accent-hover': 'darkorange',
        'accent-active': 'orangered',
      }),
    ).toEqual({
      primary: {
        light: 'hsl(var(--primary-light))',
        dark: 'hsl(var(--primary-dark))',
      },
      secondary: 'hsl(var(--secondary))',
      accent: {
        DEFAULT: 'hsl(var(--accent))',
        hover: 'hsl(var(--accent-hover))',
        active: 'hsl(var(--accent-active))',
      },
    })
  })

  it('should build tailwind theme colors from css vars', () => {
    expect(
      buildTailwindThemeColorsFromCssVars({
        'background': '0 0% 100%',
        'foreground': '224 71.4% 4.1%',
        'card': '0 0% 100%',
        'card-foreground': '224 71.4% 4.1%',
        'popover': '0 0% 100%',
        'popover-foreground': '224 71.4% 4.1%',
        'primary': '220.9 39.3% 11%',
        'primary-foreground': '210 20% 98%',
        'secondary': '220 14.3% 95.9%',
        'secondary-foreground': '220.9 39.3% 11%',
        'muted': '220 14.3% 95.9%',
        'muted-foreground': '220 8.9% 46.1%',
        'accent': '220 14.3% 95.9%',
        'accent-foreground': '220.9 39.3% 11%',
        'destructive': '0 84.2% 60.2%',
        'destructive-foreground': '210 20% 98%',
        'border': '220 13% 91%',
        'input': '220 13% 91%',
        'ring': '224 71.4% 4.1%',
      }),
    ).toEqual({
      border: 'hsl(var(--border))',
      input: 'hsl(var(--input))',
      ring: 'hsl(var(--ring))',
      background: 'hsl(var(--background))',
      foreground: 'hsl(var(--foreground))',
      primary: {
        DEFAULT: 'hsl(var(--primary))',
        foreground: 'hsl(var(--primary-foreground))',
      },
      secondary: {
        DEFAULT: 'hsl(var(--secondary))',
        foreground: 'hsl(var(--secondary-foreground))',
      },
      destructive: {
        DEFAULT: 'hsl(var(--destructive))',
        foreground: 'hsl(var(--destructive-foreground))',
      },
      muted: {
        DEFAULT: 'hsl(var(--muted))',
        foreground: 'hsl(var(--muted-foreground))',
      },
      accent: {
        DEFAULT: 'hsl(var(--accent))',
        foreground: 'hsl(var(--accent-foreground))',
      },
      popover: {
        DEFAULT: 'hsl(var(--popover))',
        foreground: 'hsl(var(--popover-foreground))',
      },
      card: {
        DEFAULT: 'hsl(var(--card))',
        foreground: 'hsl(var(--card-foreground))',
      },
    })
  })
})
