import fs from "node:fs"
import path from "node:path"

import { readBridgeConfig } from "./bridge-schema.js"
import { writeIfChanged } from "./write-if-changed.js"

/**
 * Infer TypeScript type from a JSON value.
 * Recursively handles objects and arrays.
 */
function inferType(value: unknown, indent = 0): string {
  const spaces = "  ".repeat(indent)
  const innerSpaces = "  ".repeat(indent + 1)

  if (value === null) {
    return "null"
  }

  if (value === undefined) {
    return "undefined"
  }

  if (typeof value === "string") {
    return "string"
  }

  if (typeof value === "number") {
    return "number"
  }

  if (typeof value === "boolean") {
    return "boolean"
  }

  if (Array.isArray(value)) {
    if (value.length === 0) {
      return "unknown[]"
    }
    // Infer type from first element (assume homogeneous arrays)
    const elementType = inferType(value[0], indent)
    // Check if all elements have same type
    const allSameType = value.every((v) => inferType(v, indent) === elementType)
    if (allSameType) {
      return `${elementType}[]`
    }
    // Mixed array - union of all types
    const types = [...new Set(value.map((v) => inferType(v, indent)))]
    return `(${types.join(" | ")})[]`
  }

  if (typeof value === "object") {
    const obj = value as Record<string, unknown>
    const keys = Object.keys(obj)
    if (keys.length === 0) {
      return "Record<string, unknown>"
    }

    const entries = keys.map((key) => {
      const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key)
      const valueType = inferType(obj[key], indent + 1)
      return `${innerSpaces}${safeKey}: ${valueType}`
    })

    return `{\n${entries.join("\n")}\n${spaces}}`
  }

  return "unknown"
}

/**
 * Generate a valid TypeScript interface name from a key.
 */
function toInterfaceName(key: string): string {
  // Convert to PascalCase
  const pascal = key
    .replace(/[^a-zA-Z0-9_$]/g, "_")
    .replace(/_+/g, "_")
    .replace(/^_|_$/g, "")
    .replace(/(^|_)([a-z])/g, (_, __, c) => c.toUpperCase())

  return pascal || "StaticProp"
}

/**
 * Generate `static-props.ts` from `.litestar.json` static props.
 *
 * This reads the staticProps from the bridge config and generates:
 * - An interface with all properties typed
 * - Named exports for each property
 * - Default export with all properties
 *
 * @returns true if file was changed, false if unchanged
 */
export async function emitStaticPropsTypes(outputDir: string): Promise<boolean> {
  const bridgeConfig = readBridgeConfig()
  const staticProps = bridgeConfig?.staticProps ?? {}

  const outDir = path.resolve(process.cwd(), outputDir)
  await fs.promises.mkdir(outDir, { recursive: true })
  const outFile = path.join(outDir, "static-props.ts")

  // Handle empty props
  if (Object.keys(staticProps).length === 0) {
    const emptyBody = `// AUTO-GENERATED by litestar-vite. Do not edit.
/* eslint-disable */

/**
 * Static props configured in Python ViteConfig.static_props.
 * Currently empty - add static_props to your ViteConfig to generate types.
 *
 * @example
 * config = ViteConfig(
 *     static_props={
 *         "appName": "My App",
 *         "version": "1.0.0",
 *     }
 * )
 */
export interface StaticProps {}

/** All static props */
export const staticProps: StaticProps = {}

export default staticProps
`
    const result = await writeIfChanged(outFile, emptyBody, { encoding: "utf-8" })
    return result.changed
  }

  // Generate interface properties
  const interfaceProps: string[] = []
  const namedExports: string[] = []
  const nestedInterfaces: string[] = []

  for (const [key, value] of Object.entries(staticProps)) {
    const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : JSON.stringify(key)
    const inferredType = inferType(value, 1)

    // For complex nested objects, create named interfaces
    if (typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length > 0) {
      const interfaceName = toInterfaceName(key)
      nestedInterfaces.push(`export interface ${interfaceName} ${inferType(value, 0)}`)
      interfaceProps.push(`  ${safeKey}: ${interfaceName}`)
    } else {
      interfaceProps.push(`  ${safeKey}: ${inferredType}`)
    }

    // Generate named export for valid identifiers
    if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
      namedExports.push(`export const ${key} = staticProps.${key}`)
    }
  }

  const body = `// AUTO-GENERATED by litestar-vite. Do not edit.
/* eslint-disable */

${nestedInterfaces.length > 0 ? `${nestedInterfaces.join("\n\n")}\n\n` : ""}/**
 * Static props configured in Python ViteConfig.static_props.
 * These values are set at build time and do not change at runtime.
 */
export interface StaticProps {
${interfaceProps.join("\n")}
}

/** All static props with inferred types */
export const staticProps: StaticProps = ${JSON.stringify(staticProps, null, 2)} as const satisfies StaticProps

${namedExports.join("\n")}

export default staticProps
`

  const result = await writeIfChanged(outFile, body, { encoding: "utf-8" })
  return result.changed
}
