#!/usr/bin/env node

// When new components are added (not when existing ones are changed),
// run this script to generate React, Vue, and Svelte wrappers
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

// Get the directory of this script
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const rootDir = path.resolve(scriptDir, '../../..')

// Define paths
// source:
const COMPONENTS_DIR = path.join(rootDir, 'packages/@uppy/components/src')
// destinations:
const REACT_DIR = path.join(
  rootDir,
  'packages/@uppy/react/src/headless/generated',
)
const VUE_DIR = path.join(rootDir, 'packages/@uppy/vue/src/headless/generated')
const SVELTE_DIR = path.join(
  rootDir,
  'packages/@uppy/svelte/src/lib/components/headless/generated',
)

/**
 * Parse prop names from a TypeScript component file
 * Extracts property names from the Props type definition, excluding 'ctx'
 */
async function parsePropsFromFile(filePath) {
  const content = await fs.readFile(filePath, 'utf-8')

  // Match the Props type definition: export type ComponentNameProps = { ... }
  const propsMatch = content.match(
    /export\s+type\s+\w+Props\s*=\s*\{([^}]+)\}/s,
  )
  if (!propsMatch) {
    return []
  }

  const propsBlock = propsMatch[1]

  // Extract property names (handle optional ? and required properties)
  const propNames = []
  const propRegex = /^\s*(\w+)\??:/gm
  for (const match of propsBlock.matchAll(propRegex)) {
    const propName = match[1]
    // Exclude 'ctx' as it's provided by the context
    if (propName !== 'ctx') {
      propNames.push(propName)
    }
  }

  return propNames
}

// Templates
const REACT_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import {
  %%ComponentName%% as %%PreactComponentName%%,
  type %%PropsTypeName%%,
} from '@uppy/components'
import { h as preactH, render as preactRender } from '@uppy/core/utils/preact'
import { useContext, useEffect, useRef } from 'react'
import { UppyContext } from '../UppyContextProvider.js'

export default function %%ComponentName%%(props: Omit<%%PropsTypeName%%, 'ctx'>) {
  const ref = useRef(null)
  const ctx = useContext(UppyContext)

  useEffect(() => {
    if (ref.current) {
      preactRender(
        preactH(%%PreactComponentName%%, {
          ...props,
          ctx,
        } satisfies %%PropsTypeName%%),
        ref.current,
      )
    }
  }, [ctx, props])

  return <div ref={ref} />
}
`

const VUE_TEMPLATE = `\
// This file was generated by build-components.mjs
// ANY EDITS WILL BE OVERWRITTEN!
import { defineComponent, h, onMounted, ref, watch } from 'vue'
import {
  %%ComponentName%% as %%PreactComponentName%%,
  type %%PropsTypeName%%,
} from '@uppy/components'
import { h as preactH, render as preactRender } from '@uppy/core/utils/preact'
import { useUppyContext } from '../useUppyContext.js'

export default defineComponent({
  name: '%%ComponentName%%',
  props: %%PropsArray%%,
  setup(props) {
    const containerRef = ref<HTMLElement | null>(null)
    const ctx = useUppyContext()

    function render%%ComponentName%%() {
      if (containerRef.value) {
        preactRender(
          preactH(%%PreactComponentName%%, {
            ...props,
            ctx,
          } satisfies %%PropsTypeName%%),
          containerRef.value,
        )
      }
    }

    onMounted(() => {
      render%%ComponentName%%()
    })

    watch(ctx, () => {
      render%%ComponentName%%()
    })

    watch(
      () => props,
      () => {
        render%%ComponentName%%()
      },
      { deep: true },
    )

    return () => h('div', { ref: containerRef })
  },
})
`

const SVELTE_TEMPLATE = `\
<script lang="ts">
  // This file was generated by build-components.mjs
  // ANY EDITS WILL BE OVERWRITTEN!
  import { getContext, mount } from 'svelte'
  import {
    %%ComponentName%% as %%PreactComponentName%%,
    type %%PropsTypeName%%,
    type UppyContext,
  } from '@uppy/components'
  import { h as preactH, render as preactRender } from '@uppy/core/utils/preact'
  import { UppyContextKey } from '../UppyContextProvider.svelte'

  const props: Omit<%%PropsTypeName%%, 'ctx'> = $props()
  const ctx = getContext<UppyContext>(UppyContextKey)
  let container: HTMLElement

  $effect(() => {
    if (container) {
      preactRender(
        preactH(%%PreactComponentName%%, {
          ...props,
          ctx,
        } satisfies %%PropsTypeName%%),
        container,
      )
    }
  })
</script>

<div bind:this={container}></div>
`

try {
  // Check if components directory exists
  await fs.access(COMPONENTS_DIR).catch(() => {
    throw new Error(`Components directory not found: ${COMPONENTS_DIR}`)
  })
  await Promise.all(
    [REACT_DIR, VUE_DIR, SVELTE_DIR].map(async (dir) => {
      if (!existsSync(dir)) {
        await fs.mkdir(dir, { recursive: true })
      }
    }),
  )

  // Read all files in components directory
  const files = await fs.readdir(COMPONENTS_DIR)

  // Filter for .tsx files
  const tsxFiles = files.filter((file) => file.endsWith('.tsx'))

  console.log(`Found ${tsxFiles.length} Preact component(s) to process\n`)

  // Track generated components for index files
  const reactComponents = []
  const vueComponents = []
  const svelteComponents = []

  // Process each tsx file
  for (const file of tsxFiles) {
    try {
      const componentName = path.basename(file, '.tsx')
      const propsTypeName = `${componentName}Props`
      const preactComponentName = `Preact${componentName}`
      const filePath = path.join(COMPONENTS_DIR, file)

      // Parse props from the source file
      const propNames = await parsePropsFromFile(filePath)
      const propsArray = JSON.stringify(propNames)

      // Generate React wrapper
      const reactContent = REACT_TEMPLATE.replace(
        /%%ComponentName%%/g,
        componentName,
      )
        .replace(/%%PreactComponentName%%/g, preactComponentName)
        .replace(/%%PropsTypeName%%/g, propsTypeName)

      // Generate Vue wrapper
      const vueContent = VUE_TEMPLATE.replace(
        /%%ComponentName%%/g,
        componentName,
      )
        .replace(/%%PreactComponentName%%/g, preactComponentName)
        .replace(/%%PropsTypeName%%/g, propsTypeName)
        .replace(/%%PropsArray%%/g, propsArray)

      // Generate Svelte wrapper
      const svelteContent = SVELTE_TEMPLATE.replace(
        /%%ComponentName%%/g,
        componentName,
      )
        .replace(/%%PreactComponentName%%/g, preactComponentName)
        .replace(/%%PropsTypeName%%/g, propsTypeName)

      // Write files
      const reactFilePath = path.join(REACT_DIR, `${componentName}.tsx`)
      const vueFilePath = path.join(VUE_DIR, `${componentName}.ts`)
      const svelteFilePath = path.join(SVELTE_DIR, `${componentName}.svelte`)

      await fs.writeFile(reactFilePath, reactContent)
      await fs.writeFile(vueFilePath, vueContent)
      await fs.writeFile(svelteFilePath, svelteContent)

      // Add to component lists for index files
      reactComponents.push(componentName)
      vueComponents.push(componentName)
      svelteComponents.push(componentName)

      console.log(
        `√ ${componentName} (props: ${propNames.join(', ') || 'none'})`,
      )
    } catch (error) {
      console.error(`Error processing component ${file}:`, error)
    }
  }

  // Generate index files
  if (reactComponents.length > 0) {
    const reactIndexContent = reactComponents
      .map((name) => `export { default as ${name} } from './${name}.js'`)
      .join('\n')
    await fs.writeFile(
      path.join(REACT_DIR, 'index.ts'),
      `${reactIndexContent}\n`,
    )
    console.log(`\nExporting React components from ${REACT_DIR}`)
  }

  if (vueComponents.length > 0) {
    const vueIndexContent = vueComponents
      .map((name) => `export { default as ${name} } from './${name}.js'`)
      .join('\n')
    await fs.writeFile(path.join(VUE_DIR, 'index.ts'), `${vueIndexContent}\n`)
    console.log(`Exporting Vue components from ${VUE_DIR}`)
  }

  if (svelteComponents.length > 0) {
    const svelteIndexContent = svelteComponents
      .map((name) => `export { default as ${name} } from './${name}.svelte'`)
      .join('\n')
    await fs.writeFile(
      path.join(SVELTE_DIR, 'index.ts'),
      `${svelteIndexContent}\n`,
    )
    console.log(`Exporting Svelte components from ${SVELTE_DIR}`)
  }

  console.log('\nAll wrappers and index files generated successfully!')
} catch (error) {
  console.error('Error generating wrappers:', error)
  process.exit(1)
}
