import { readFileSync, writeFileSync } from 'node:fs'
import MagicString from 'magic-string'
import { parseSync } from 'oxc-parser'
import { walk } from './ast-utils'
import { isTanStackDevtoolsImport } from './devtools-packages'
import type { Node } from 'oxc-parser'
import type { PluginInjection } from '@tanstack/devtools-client'

/**
 * Detects if a file imports TanStack devtools packages
 */
const detectDevtoolsImport = (code: string): boolean => {
  try {
    const result = parseSync('input.tsx', code, {
      sourceType: 'module',
      lang: 'tsx',
    })
    if (result.errors.length > 0) return false

    let hasDevtoolsImport = false

    walk(result.program, (node) => {
      if (hasDevtoolsImport) return
      if (
        node.type === 'ImportDeclaration' &&
        isTanStackDevtoolsImport(node.source.value)
      ) {
        hasDevtoolsImport = true
      }
    })

    return hasDevtoolsImport
  } catch (e) {
    return false
  }
}

/**
 * Finds the TanStackDevtools component name in the file
 * Handles renamed imports and namespace imports
 */
export const findDevtoolsComponentName = (code: string): string | null => {
  try {
    const result = parseSync('input.tsx', code, {
      sourceType: 'module',
      lang: 'tsx',
    })
    if (result.errors.length > 0) return null

    let componentName: string | null = null

    walk(result.program, (node) => {
      if (componentName) return
      if (node.type !== 'ImportDeclaration') return
      if (!isTanStackDevtoolsImport(node.source.value)) return

      for (const spec of node.specifiers) {
        // import { TanStackDevtools } or import { TanStackDevtools as X }
        if (
          spec.type === 'ImportSpecifier' &&
          spec.imported.type === 'Identifier' &&
          spec.imported.name === 'TanStackDevtools'
        ) {
          componentName = spec.local.name
          return
        }
        // import * as X from '...'
        if (spec.type === 'ImportNamespaceSpecifier') {
          componentName = `${spec.local.name}.TanStackDevtools`
          return
        }
      }
    })

    return componentName
  } catch (e) {
    return null
  }
}

/**
 * Check if a plugin already exists in the array expression
 */
function pluginExists(
  code: string,
  node: Node,
  importName: string,
  displayName: string,
  pluginType: string,
): boolean {
  if (node.type !== 'ArrayExpression') return false

  for (const element of node.elements) {
    if (!element) continue

    if (pluginType === 'function') {
      if (
        element.type === 'CallExpression' &&
        element.callee.type === 'Identifier' &&
        element.callee.name === importName
      ) {
        return true
      }
    } else {
      if (element.type !== 'ObjectExpression') continue
      for (const prop of element.properties) {
        if (
          prop.type === 'Property' &&
          prop.key.type === 'Identifier' &&
          prop.key.name === 'name' &&
          prop.value.type === 'Literal' &&
          code.slice(prop.value.start + 1, prop.value.end - 1) === displayName
        ) {
          return true
        }
      }
    }
  }

  return false
}

function buildPluginString(
  importName: string,
  displayName: string,
  pluginType: string,
): string {
  if (pluginType === 'function') {
    return `${importName}()`
  }
  return `{ name: ${JSON.stringify(displayName)}, render: <${importName} /> }`
}

export const transformAndInject = (
  code: string,
  injection: PluginInjection,
  devtoolsComponentName: string,
): { code: string; transformed: boolean } | null => {
  const importName = injection.pluginImport?.importName
  const pluginType = injection.pluginImport?.type || 'jsx'
  const displayName = injection.pluginName

  if (!importName) {
    return null
  }

  try {
    const result = parseSync('input.tsx', code, {
      sourceType: 'module',
      lang: 'tsx',
    })
    if (result.errors.length > 0) return null

    const s = new MagicString(code)
    const isNamespaceImport = devtoolsComponentName.includes('.')

    walk(result.program, (node) => {
      if (node.type !== 'JSXOpeningElement') return

      let matches = false
      if (isNamespaceImport) {
        if (node.name.type === 'JSXMemberExpression') {
          const fullName = `${node.name.object.type === 'JSXIdentifier' ? node.name.object.name : ''}.${node.name.property.name}`
          matches = fullName === devtoolsComponentName
        }
      } else {
        matches =
          node.name.type === 'JSXIdentifier' &&
          node.name.name === devtoolsComponentName
      }

      if (!matches) return

      // Find the plugins prop
      const pluginsProp = node.attributes.find(
        (attr) =>
          attr.type === 'JSXAttribute' &&
          attr.name.type === 'JSXIdentifier' &&
          attr.name.name === 'plugins',
      )

      if (pluginsProp && pluginsProp.type === 'JSXAttribute') {
        if (
          pluginsProp.value &&
          pluginsProp.value.type === 'JSXExpressionContainer'
        ) {
          const expression = pluginsProp.value.expression
          if (expression.type === 'ArrayExpression') {
            if (
              !pluginExists(
                code,
                expression,
                importName,
                displayName,
                pluginType,
              )
            ) {
              const pluginStr = buildPluginString(
                importName,
                displayName,
                pluginType,
              )
              // Insert before closing ']'
              const arrayEnd = expression.end - 1
              let prefix = ''
              if (expression.elements.length > 0) {
                const arrayContent = code.slice(expression.start + 1, arrayEnd)
                prefix = arrayContent.trimEnd().endsWith(',') ? ' ' : ', '
              }
              s.appendLeft(arrayEnd, prefix + pluginStr)
            }
          }
        }
      } else {
        // No plugins prop — create one
        const pluginStr = buildPluginString(importName, displayName, pluginType)
        const attrStr = ` plugins={[${pluginStr}]}`
        if (node.selfClosing) {
          s.appendLeft(node.end - 2, attrStr)
        } else {
          s.appendLeft(node.end - 1, attrStr)
        }
      }
    })

    // Add import at the top of the file if transform happened
    if (s.hasChanged()) {
      const state = { lastImportEnd: 0, alreadyImported: false }
      walk(result.program, (n) => {
        if (n.type !== 'ImportDeclaration') return
        if (n.end > state.lastImportEnd) state.lastImportEnd = n.end
        if (n.source.value !== injection.packageName) return
        for (const spec of n.specifiers) {
          if (
            spec.type === 'ImportSpecifier' &&
            spec.imported.type === 'Identifier' &&
            spec.imported.name === importName
          ) {
            state.alreadyImported = true
          }
        }
      })
      if (!state.alreadyImported) {
        const importStr = `\nimport { ${importName} } from ${JSON.stringify(injection.packageName)};`
        s.appendRight(state.lastImportEnd, importStr)
      }
    }

    return { code: s.toString(), transformed: s.hasChanged() }
  } catch (e) {
    return null
  }
}

/**
 * Detects if a file contains TanStack devtools import
 */
export function detectDevtoolsFile(code: string): boolean {
  return detectDevtoolsImport(code)
}

/**
 * Injects a plugin into the TanStackDevtools component in a file
 * Reads the file, transforms it, and writes it back
 */
export function injectPluginIntoFile(
  filePath: string,
  injection: PluginInjection,
): { success: boolean; error?: string } {
  try {
    const code = readFileSync(filePath, 'utf-8')

    const devtoolsComponentName = findDevtoolsComponentName(code)
    if (!devtoolsComponentName) {
      return {
        success: false,
        error: 'Could not find TanStackDevtools import',
      }
    }

    const result = transformAndInject(code, injection, devtoolsComponentName)

    if (!result?.transformed) {
      return {
        success: false,
        error: 'Plugin already exists or no TanStackDevtools component found',
      }
    }

    writeFileSync(filePath, result.code, 'utf-8')

    return { success: true }
  } catch (e) {
    console.error('Error injecting plugin:', e)
    return {
      success: false,
      error: e instanceof Error ? e.message : 'Unknown error',
    }
  }
}
