import type {
  SchemaDefinition,
  SchemaRegistry,
} from "@c/validation/schemaTypes"
import type { TSchema } from "@sinclair/typebox"
import type { FastifyInstance } from "fastify"
import fs from "fs/promises"
import path from "path"

const generateRegisteredSchemas = async (fastify: FastifyInstance) => {
  try {
    const sharedDir = path.join(__dirname, "shared")
    await generateSharedSchemas(sharedDir)
    fastify.log.info("Successfully generated shared schema files")
  } catch (error) {
    fastify.log.error("Failed to generate shared schema files:", error)
    // Don't throw - we don't want to prevent server startup
  }
}

// Main generation function
export const generateSharedSchemas = async (sharedDir: string) => {
  const schemas = schemaRegistry.getAll()

  // Group schemas by type (core vs plugin)
  const pluginSchemas = new Map<string, SchemaDefinition[]>()
  const coreSchemas: SchemaDefinition[] = []

  schemas.forEach((schema) => {
    if (schema.isCore) {
      coreSchemas.push(schema)
    } else if (schema.pluginKey) {
      const schemas = pluginSchemas.get(schema.pluginKey) || []
      schemas.push(schema)
      pluginSchemas.set(schema.pluginKey, schemas)
    }
  })

  // Create base directories
  await fs.mkdir(path.join(sharedDir, "types/core"), { recursive: true })
  await fs.mkdir(path.join(sharedDir, "types/plugins"), { recursive: true })
  await fs.mkdir(path.join(sharedDir, "schemas/core"), { recursive: true })
  await fs.mkdir(path.join(sharedDir, "schemas/plugins"), { recursive: true })

  // Generate core schemas
  for (const schema of coreSchemas) {
    const schemaContent = generateSchemaFile(schema)
    const typesContent = generateTypesFile(schema)

    await fs.writeFile(
      path.join(sharedDir, `schemas/core/${schema.name}.schema.ts`),
      schemaContent,
    )
    await fs.writeFile(
      path.join(sharedDir, `types/core/${schema.name}.types.ts`),
      typesContent,
    )
  }

  // Generate plugin schemas
  for (const [pluginKey, schemas] of pluginSchemas.entries()) {
    // Create plugin-specific directories
    const pluginDir = path.join(sharedDir, `types/plugins/${pluginKey}`)
    const pluginSchemaDir = path.join(sharedDir, `schemas/plugins/${pluginKey}`)
    await fs.mkdir(pluginDir, { recursive: true })
    await fs.mkdir(pluginSchemaDir, { recursive: true })

    // Generate index file with namespace
    const indexContent = generatePluginIndexFile(pluginKey, schemas)
    await fs.writeFile(path.join(pluginDir, "index.ts"), indexContent)

    // Generate individual schema files
    for (const schema of schemas) {
      const schemaContent = generateSchemaFile(schema)
      await fs.writeFile(
        path.join(pluginSchemaDir, `${schema.name}.schema.ts`),
        schemaContent,
      )
    }
  }

  // Generate index files
  await generateCoreTypesIndex(sharedDir)
  if (process.env.RACLETTE_CORE_ABSOLUTE_PATH?.includes("node_modules")) {
    // only generate this for non-linked core to prevent mixup of app content in code
    await generatePluginTypesIndex(sharedDir)
  }
  await generateMainTypesIndex(sharedDir)
}

const createSchemaRegistry = (): SchemaRegistry => {
  const definitions = new Map<string, SchemaDefinition>()

  return {
    register: (def: SchemaDefinition): void => {
      definitions.set(def.name, def)
    },
    getAll: (): SchemaDefinition[] => {
      return Array.from(definitions.values())
    },
  }
}

const schemaRegistry = createSchemaRegistry()

// Registration function
export const registerSchemaDefinition = (options: SchemaDefinition) => {
  const { schema, publicSchema, name, isCore, pluginKey } = options

  // For plugins, pluginKey is required
  if (!isCore && !pluginKey) {
    throw new Error("Plugin schemas must provide a pluginKey")
  }

  schemaRegistry.register({
    schema,
    publicSchema,
    name,
    isCore,
    pluginKey,
  })
}

const formatAsObjectLiteral = (obj: any, indent = 0): string => {
  // TODO: make this more stable
  if (!obj.required) {
    obj.required = []
  }

  return JSON.stringify(obj, null, indent)
    .replace(/"Type\.(\w+)\((.*?)\)"/g, "Type.$1($2)") // Convert Type functions back to actual calls
    .replace(/"(\$id|title)":/g, "$1:") // Fix $id and title properties
    .replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)"\s*:/g, "$1:") // Remove quotes from object keys
    .replace(/(\n\s*)}/g, ",$1}") // Add trailing comma before closing braces
    .replace(/(\n\s*)]/g, ",$1]") // Add trailing comma before closing brackets
}

// Generation helper functions
const generateWarningHeader = (): string => `/**
 * ⚠️ WARNING: THIS FILE IS AUTO-GENERATED ⚠️
 * 
 * This file is generated by the type generation system.
 * Any manual changes will be overwritten on next server start and should always be committed.
 * 
 */`

const generateSchemaFile = (schema: SchemaDefinition): string => {
  const schemaJson = formatAsObjectLiteral(schema.schema, 2)

  return `${generateWarningHeader()}

import { Type } from "@sinclair/typebox"

export const ${schema.name}Schema = ${schemaJson}

${schema.publicSchema ? `export const Public${schema.name}Schema = ${formatAsObjectLiteral(schema.publicSchema, 2)}` : ""}

export default ${schema.name}Schema
`
}

const schemaToTS = (schema: TSchema): string => {
  if (schema.type === "object" && schema.properties) {
    const properties = Object.entries(schema.properties)
      .map(([key, prop]: [string, any]) => {
        const optional = !schema.required?.includes(key)
        const type = propertyToTS(prop)
        return `  ${key}${optional ? "?" : ""}: ${type}`
      })
      .join("\n")

    return `{\n${properties}\n}`
  }
  return "any"
}

const propertyToTS = (prop: any): string => {
  switch (prop.type) {
    case "string":
      return prop.format === "date-time" ? "Date" : "string"
    case "number":
      return "number"
    case "boolean":
      return "boolean"
    case "array":
      return `${propertyToTS(prop.items)}[]`
    case "object":
      return schemaToTS(prop)
    case "null":
      return "null"
    default:
      if (prop.anyOf || prop.oneOf) {
        const types = (prop.anyOf || prop.oneOf).map(propertyToTS)
        return types.join(" | ")
      }
      return "any"
  }
}

const generateTypesFile = (schema: SchemaDefinition): string => {
  const tsInterface = schemaToTS(schema.schema)
  const publicTsInterface = schema.publicSchema
    ? schemaToTS(schema.publicSchema)
    : null

  return `${generateWarningHeader()}

export type ${schema.name} = ${tsInterface}

${publicTsInterface ? `export type Public${schema.name} = ${publicTsInterface}` : ""}
`
}

const generateTypesFileContent = (body: string): string => {
  const trimmedBody = body.trim()
  if (!trimmedBody) {
    return "export type {}\n"
  }

  return `${generateWarningHeader()}\n\n${trimmedBody}\n`
}

const generatePluginIndexFile = (
  pluginKey: string,
  schemas: SchemaDefinition[],
): string => {
  const imports = schemas
    .map(
      (schema) =>
        `import { ${schema.name}Schema${schema.publicSchema ? `, Public${schema.name}Schema` : ""} } from '../../../schemas/plugins/${pluginKey}/${schema.name}.schema'`,
    )
    .join("\n")

  const types = schemas
    .map((schema) => {
      const tsInterface = schemaToTS(schema.schema)
      const publicTsInterface = schema.publicSchema
        ? schemaToTS(schema.publicSchema)
        : null

      return ` export interface ${schema.name} ${tsInterface}

${publicTsInterface ? `export interface Public${schema.name} ${publicTsInterface}` : ""}`
    })
    .join("\n\n")

  return generateTypesFileContent(`import type { Static } from '@sinclair/typebox'
${imports}

export namespace ${pluginKey} {
${types}
}`)
}

// Helper function to generate the core types index.ts
const generateCoreTypesIndex = async (sharedDir: string) => {
  const coreDir = path.join(sharedDir, "types/core")
  const typeFiles = await fs.readdir(coreDir)

  // Filter for type files only
  const coreTypeFiles = typeFiles.filter(
    (file) => file.endsWith(".types.ts") && file !== "index.ts",
  )

  // Create export statements
  const exportStatements = coreTypeFiles
    .map((file) => {
      const moduleName = path.basename(file, ".types.ts")
      return `export type * from './${moduleName}.types'`
    })
    .join("\n")

  // Write index file
  await fs.writeFile(
    path.join(coreDir, "index.ts"),
    generateTypesFileContent(exportStatements),
  )
}

// Helper function to generate the plugins index.ts
const generatePluginTypesIndex = async (sharedDir: string) => {
  const pluginsDir = path.join(sharedDir, "types/plugins")
  const pluginDirs = await fs.readdir(pluginsDir)

  // Filter for directories only
  const pluginFolders = await Promise.all(
    pluginDirs.map(async (dir) => {
      const stat = await fs.stat(path.join(pluginsDir, dir))
      return { dir, isDir: stat.isDirectory() }
    }),
  )

  const validPluginDirs = pluginFolders
    .filter(({ isDir }) => isDir)
    .map(({ dir }) => dir)

  // Create export statements
  const exportStatements = validPluginDirs
    .map((pluginKey) => `export type * from './${pluginKey}'`)
    .join("\n")

  // Write index file
  await fs.writeFile(
    path.join(pluginsDir, "index.ts"),
    generateTypesFileContent(exportStatements),
  )
}

// Helper function to generate the main types/index.ts
const generateMainTypesIndex = async (sharedDir: string) => {
  const content =
    generateTypesFileContent(`// Import and re-export all core types
export type * from './core'

// Import and re-export all plugin types
export type * from './plugins'`)

  await fs.writeFile(path.join(sharedDir, "types/index.ts"), content)
}

export default generateRegisteredSchemas
