/**
 * Tests for the Inertia page props type generation.
 *
 * These tests verify the output of emitPagePropsTypes() which generates
 * TypeScript type definitions from inertia-pages.json metadata.
 */

import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { describe, expect, it } from "vitest"

// Import the interface type for our test data
interface InertiaPagePropsJson {
  pages: Record<
    string,
    {
      route: string
      propsType?: string
      tsType?: string
      customTypes?: string[]
      schemaRef?: string
      handler?: string
    }
  >
  sharedProps: Record<string, { type: string; optional?: boolean }>
  typeGenConfig: {
    includeDefaultAuth: boolean
    includeDefaultFlash: boolean
  }
  typeImportPaths?: Record<string, string>
  fallbackType?: "unknown" | "any"
  generatedAt: string
}

/**
 * Helper to create a temporary directory for test output.
 */
function _createTempDir(): string {
  return fs.mkdtempSync(path.join(os.tmpdir(), "litestar-vite-test-"))
}

/**
 * Helper to create a test inertia-pages.json file.
 */
function _createTestPagesJson(tmpDir: string, data: InertiaPagePropsJson): string {
  const pagesPath = path.join(tmpDir, "inertia-pages.json")
  fs.writeFileSync(pagesPath, JSON.stringify(data, null, 2))
  return pagesPath
}

/**
 * Dynamically import the emitPagePropsTypes function.
 * We need to do this because the function is not exported from the main module.
 * Instead, we'll test the output format by simulating what it produces.
 */
function generatePagePropsOutput(json: InertiaPagePropsJson): string {
  const { includeDefaultAuth, includeDefaultFlash } = json.typeGenConfig
  const fallbackType = json.fallbackType ?? "unknown"
  const defaultFallback = fallbackType === "any" ? "Record<string, any>" : "Record<string, unknown>"

  // Build default types based on config
  let userTypes = ""
  let authTypes = ""
  let flashTypes = ""

  if (includeDefaultAuth) {
    userTypes = `/**
 * Default User interface - minimal baseline for common auth patterns.
 * Users extend this via module augmentation with their full user model.
 *
 * @example
 * declare module 'litestar-vite-plugin/inertia' {
 *   interface User {
 *     avatarUrl?: string | null
 *     roles: Role[]
 *     teams: Team[]
 *   }
 * }
 */
export interface User {
  id: string
  email: string
  name?: string | null
}

`
    authTypes = `/**
 * Default AuthData interface - mirrors Laravel Jetstream pattern.
 * isAuthenticated + optional user is the universal pattern.
 */
export interface AuthData {
  isAuthenticated: boolean
  user?: User
}

`
  } else {
    userTypes = `/**
 * User interface - define via module augmentation.
 * Default auth types are disabled.
 *
 * @example
 * declare module 'litestar-vite-plugin/inertia' {
 *   interface User {
 *     uuid: string
 *     username: string
 *   }
 * }
 */
export interface User {}

`
    authTypes = `/**
 * AuthData interface - define via module augmentation.
 * Default auth types are disabled.
 */
export interface AuthData {}

`
  }

  if (includeDefaultFlash) {
    flashTypes = `/**
 * Default FlashMessages interface - category to messages mapping.
 * Standard categories: success, error, info, warning.
 */
export interface FlashMessages {
  [category: string]: string[]
}

`
  } else {
    flashTypes = `/**
 * FlashMessages interface - define via module augmentation.
 * Default flash types are disabled.
 */
export interface FlashMessages {}

`
  }

  const defaultGeneratedSharedProps: InertiaPagePropsJson["sharedProps"] = {
    errors: { type: "Record<string, string[]>", optional: true },
    csrf_token: { type: "string", optional: true },
    ...(includeDefaultAuth || includeDefaultFlash
      ? {
          auth: { type: "AuthData", optional: true },
          flash: { type: "FlashMessages", optional: true },
        }
      : {}),
  }

  const generatedSharedProps = Object.keys(json.sharedProps ?? {}).length > 0 ? json.sharedProps : defaultGeneratedSharedProps

  const generatedSharedPropLines = Object.entries(generatedSharedProps)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([key, def]) => {
      const optional = def.optional ? "?" : ""
      const safeKey = /^[$A-Z_][0-9A-Z_$]*$/i.test(key) ? key : JSON.stringify(key)
      return `  ${safeKey}${optional}: ${def.type}`
    })

  // Build page props entries
  const pageEntries: string[] = []
  for (const [component, data] of Object.entries(json.pages)) {
    const rawType = data.tsType || data.propsType || defaultFallback
    const propsType = rawType.includes("|") ? `(${rawType})` : rawType
    pageEntries.push(`  "${component}": ${propsType} & FullSharedProps`)
  }

  return `// AUTO-GENERATED by litestar-vite. Do not edit.
/* eslint-disable */

${userTypes}${authTypes}${flashTypes}/**
 * Generated shared props (always present).
 * Includes built-in props + static config props.
 */
export interface GeneratedSharedProps {
${generatedSharedPropLines.join("\n")}
}

/**
 * User-defined shared props for dynamic share() calls in guards/middleware.
 * Extend this interface via module augmentation.
 *
 * @example
 * declare module 'litestar-vite-plugin/inertia' {
 *   interface User {
 *     avatarUrl?: string | null
 *     roles: Role[]
 *     teams: Team[]
 *   }
 *   interface SharedProps {
 *     locale?: string
 *     currentTeam?: CurrentTeam
 *   }
 * }
 */
export interface SharedProps {
}

/** Full shared props = generated + user-defined */
export type FullSharedProps = GeneratedSharedProps & SharedProps

/** Page props mapped by component name */
export interface PageProps {
${pageEntries.join("\n")}
}

/** Component name union type */
export type ComponentName = keyof PageProps

/** Type-safe props for a specific component */
export type InertiaPageProps<C extends ComponentName> = PageProps[C]

/** Get props type for a specific page component */
export type PagePropsFor<C extends ComponentName> = PageProps[C]

// Re-export for module augmentation
declare module "litestar-vite-plugin/inertia" {
  export { User, AuthData, FlashMessages, SharedProps, GeneratedSharedProps, FullSharedProps, PageProps, ComponentName, InertiaPageProps, PagePropsFor }
}
`
}

describe("Page Props Type Generation", () => {
  describe("Output Structure", () => {
    it("generates valid TypeScript header", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("// AUTO-GENERATED by litestar-vite. Do not edit.")
      expect(output).toContain("/* eslint-disable */")
    })

    it("parenthesizes union props types before intersection", () => {
      const json: InertiaPagePropsJson = {
        pages: {
          "auth/register": { route: "/auth/register", tsType: "Response | Record<string, unknown>" },
        },
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: false, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)
      expect(output).toContain(`"auth/register": (Response | Record<string, unknown>) & FullSharedProps`)
    })

    it("generates GeneratedSharedProps interface", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface GeneratedSharedProps")
      expect(output).toContain("errors?: Record<string, string[]>")
      expect(output).toContain("auth?: AuthData")
      expect(output).toContain("flash?: FlashMessages")
      expect(output).toContain("csrf_token?: string")
    })

    it("generates SharedProps interface", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface SharedProps")
    })

    it("generates FullSharedProps type alias", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export type FullSharedProps = GeneratedSharedProps & SharedProps")
    })

    it("generates PageProps interface", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface PageProps")
    })

    it("generates ComponentName type", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export type ComponentName = keyof PageProps")
    })

    it("generates InertiaPageProps helper type", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export type InertiaPageProps<C extends ComponentName> = PageProps[C]")
    })

    it("generates module augmentation declaration", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain('declare module "litestar-vite-plugin/inertia"')
    })
  })

  describe("Auth Types", () => {
    it("includes User interface when includeDefaultAuth is true", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface User {")
      expect(output).toContain("id: string")
      expect(output).toContain("email: string")
      expect(output).toContain("name?: string | null")
    })

    it("includes AuthData interface when includeDefaultAuth is true", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface AuthData {")
      expect(output).toContain("isAuthenticated: boolean")
      expect(output).toContain("user?: User")
    })

    it("generates empty User interface when includeDefaultAuth is false", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: false, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface User {}")
      expect(output).toContain("Default auth types are disabled")
    })

    it("generates empty AuthData interface when includeDefaultAuth is false", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: false, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface AuthData {}")
    })
  })

  describe("Flash Types", () => {
    it("includes FlashMessages interface when includeDefaultFlash is true", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: false, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface FlashMessages {")
      expect(output).toContain("[category: string]: string[]")
    })

    it("generates empty FlashMessages interface when includeDefaultFlash is false", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: false, includeDefaultFlash: false },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain("export interface FlashMessages {}")
      expect(output).toContain("Default flash types are disabled")
    })
  })

  describe("Shared Props Metadata", () => {
    it("keeps SharedProps empty (module augmentation)", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)
      expect(output).toMatch(/export interface SharedProps \{\s*\}/)
    })

    it("renders configured sharedProps into GeneratedSharedProps", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {
          locale: { type: "string", optional: true },
          currentTeam: { type: "Team", optional: true },
        },
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)
      expect(output).toContain("locale?: string")
      expect(output).toContain("currentTeam?: Team")
    })
  })

  describe("Page Props Mapping", () => {
    it("generates PageProps entries for each component", () => {
      const json: InertiaPagePropsJson = {
        pages: {
          Home: { route: "/", propsType: "HomeProps" },
          "Books/Index": { route: "/books", propsType: "BooksProps" },
        },
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain('"Home": HomeProps & FullSharedProps')
      expect(output).toContain('"Books/Index": BooksProps & FullSharedProps')
    })

    it("uses Record<string, unknown> when propsType is missing", () => {
      const json: InertiaPagePropsJson = {
        pages: {
          Dashboard: { route: "/dashboard" },
        },
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain('"Dashboard": Record<string, unknown> & FullSharedProps')
    })

    it("handles empty pages object", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      // PageProps should be empty
      expect(output).toMatch(/export interface PageProps \{\s*\}/)
    })

    it("handles component names with special characters", () => {
      const json: InertiaPagePropsJson = {
        pages: {
          "Admin/Users/Index": { route: "/admin/users", propsType: "AdminUsersProps" },
          "Public/FAQ": { route: "/faq", propsType: "FAQProps" },
        },
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      expect(output).toContain('"Admin/Users/Index": AdminUsersProps & FullSharedProps')
      expect(output).toContain('"Public/FAQ": FAQProps & FullSharedProps')
    })
  })

  describe("TypeScript Validity", () => {
    it("generates balanced braces", () => {
      const json: InertiaPagePropsJson = {
        pages: {
          Home: { route: "/", propsType: "HomeProps" },
          About: { route: "/about", propsType: "AboutProps" },
        },
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      const openBraces = (output.match(/{/g) || []).length
      const closeBraces = (output.match(/}/g) || []).length
      expect(openBraces).toBe(closeBraces)
    })

    it("generates balanced angle brackets for generics", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      const openAngle = (output.match(/</g) || []).length
      const closeAngle = (output.match(/>/g) || []).length
      expect(openAngle).toBe(closeAngle)
    })

    it("includes proper exports", () => {
      const json: InertiaPagePropsJson = {
        pages: {},
        sharedProps: {},
        typeGenConfig: { includeDefaultAuth: true, includeDefaultFlash: true },
        generatedAt: new Date().toISOString(),
      }

      const output = generatePagePropsOutput(json)

      // All exported types should be properly exported
      expect(output).toContain("export interface User")
      expect(output).toContain("export interface AuthData")
      expect(output).toContain("export interface FlashMessages")
      expect(output).toContain("export interface GeneratedSharedProps")
      expect(output).toContain("export interface SharedProps")
      expect(output).toContain("export type FullSharedProps")
      expect(output).toContain("export interface PageProps")
      expect(output).toContain("export type ComponentName")
      expect(output).toContain("export type InertiaPageProps")
      expect(output).toContain("export type PagePropsFor")
    })
  })
})
