import type { GetTransform } from '@vxrn/compiler'
import type { metroPlugin } from '@vxrn/vite-plugin-metro'
import type {
  AutoDepOptimizationOptions,
  DepPatch,
  AfterBuildProps as VXRNAfterBuildProps,
  VXRNBuildOptions,
  VXRNOptions,
} from 'vxrn'
import type { One as OneShared } from '../interfaces/router'
import type { RouteNode } from '../router/Route'
import type { EnvironmentGuardOptions } from './plugins/environmentGuardPlugin'

type MetroPluginOptions = Parameters<typeof metroPlugin>[0]

export type DeployTarget = 'node' | 'vercel' | 'cloudflare'

export type DeployConfig = {
  target: DeployTarget
  /** production URL, auto-detected from package name for cloudflare if not set */
  url?: string
}

export type RouteInfo<TRegex = string> = {
  file: string
  page: string
  namedRegex: TRegex
  loaderPath?: string
  loaderServerPath?: string
  urlPath: string
  urlCleanPath: string
  routeKeys: Record<string, string>
  layouts?: RouteNode[]
  middlewares?: RouteNode[]
  type: One.RouteType
  isNotFound?: boolean
  // when explicitly false, the loader dispatch short-circuits with an empty
  // module instead of importing the route's server bundle. avoids evaluating
  // page modules in workerd for routes that have no loader export.
  hasLoader?: boolean
}

export namespace One {
  export type Options = Omit<VXRNOptions, keyof PluginOptions> & PluginOptions

  export type RouteRenderMode = 'ssg' | 'spa' | 'ssr'

  export type RouteType = RouteRenderMode | 'api' | 'layout'

  export type ClientData = Record<string, any>

  export type RouteOptions = {
    routeModes?: Record<string, RouteRenderMode>
  }

  // todo move into vxrn
  export type FixDependencies = {
    [key: string]: DepPatch['patchFiles']
  }

  type PluginPlatformTarget = 'native' | 'web'

  type CompilerEnvironment = 'ios' | 'android' | 'client' | 'ssr'

  type ReactCompilerFilter =
    | boolean
    | RegExp
    | ((id: string, environment: CompilerEnvironment) => boolean)

  type ReactCompilerConfig =
    | boolean
    | PluginPlatformTarget
    | RegExp
    | ((id: string, environment: CompilerEnvironment) => boolean)
    | {
        web?: ReactCompilerFilter
        native?: ReactCompilerFilter
      }

  export type PluginOptions = {
    /**
     * Per-environment module aliases applied via resolveId so they work
     * during both vite transforms AND rolldown dep pre-bundling (where
     * resolve.alias is not applied).
     *
     * @example
     * ```ts
     * one({
     *   alias: {
     *     web: {
     *       'react-native-web': '@tamagui/react-native-web-lite',
     *     },
     *   },
     * })
     * ```
     */
    alias?: {
      /** applies to client + ssr */
      web?: Record<string, string>
      /** applies to ios + android */
      native?: Record<string, string>
      /** overrides web */
      client?: Record<string, string>
      /** overrides web */
      ssr?: Record<string, string>
      /** overrides native */
      ios?: Record<string, string>
      /** overrides native */
      android?: Record<string, string>
    }

    /**
     * Enabling zero does a couple very simple things:
     *
     *   - It makes zero hand of seamelessly from server to client without flicker
     *
     */
    // zero?: boolean

    /**
     * Per-file control over how code transforms.
     * Defaults to SWC, runs babel before SWC if:
     *
     *  - options.react.compiler is `true`, on tsx files in your app
     *  - `react-native-reanimated` is in your dependencies and a file contains a reanimated keyword
     *
     * Otherwise One defaults to using `@swc/core`.
     *
     * Accepts a function:
     *
     *   (props: {
     *      id: string
     *      code: string
     *      development: boolean
     *      environment: Environment
     *      reactForRNVersion: '18' | '19'
     *   }) =>
     *      | true     // default transforms
     *      | false    // no transforms
     *      | 'babel'  // force babel default transform
     *      | 'swc'    // force swc default transform
     *
     *       // force babel, custom transform
     *
     *      | {
     *          transform: 'babel'
     *          excludeDefaultPlugins?: boolean
     *        } & babel.TransformOptions
     *
     *      // force swc, custom transform
     *
     *      | {
     *          transform: 'swc'
     *        } & SWCOptions
     *
     * Babel defaults to preset `@babel/preset-typescript` with plugins:
     *
     *  - @babel/plugin-transform-destructuring
     *  - @babel/plugin-transform-runtime
     *  - @babel/plugin-transform-react-jsx
     *  - @babel/plugin-transform-async-generator-functions
     *  - @babel/plugin-transform-async-to-generator
     *
     *
     * SWC defaults to target es5 for native, es2020 for web.
     *
     */
    transform?: GetTransform

    // compiler?: {
    //   workletTransform?: 'reanimated' | 'worklets'
    // }

    router?: {
      /**
       * An array of globs that One uses to ignore files in your routes directory. If a file matches any pattern, it will not be treated as a route.
       *
       * This is useful for ignoring test or utility files you wish to colocate.
       *
       * Currently, we only support patterns starting with <code>&#42;&#42;/&#42;</code>.
       *
       * Example:
       * * <code>&#42;&#42;/&#42;.test.*</code>
       */
      ignoredRouteFiles?: Array<`**/*${string}`>
      /**
       * Dangerously customize the router root directory. This may lead to unexpected behavior.
       */
      root?: string
      experimental?: {
        /**
         * Auto-generate route type helpers in route files.
         *
         * Route types are always generated in routes.d.ts. This option controls whether
         * One automatically inserts type helpers into your route files.
         *
         * Options:
         * - `false` (default): No auto-generation, manually add types yourself
         * - `'type'`: Auto-inserts type-only helpers:
         *   ```typescript
         *   import type { RouteType } from 'one'
         *   type Route = RouteType<'/your/[route]'>
         *   ```
         * - `'runtime'`: Auto-inserts runtime helpers:
         *   ```typescript
         *   import { createRoute } from 'one'
         *   const route = createRoute<'/your/[route]'>()
         *   ```
         *
         * The insertion happens automatically when route files are created or modified,
         * and respects your existing code (won't modify loaders, etc).
         *
         * @default false
         */
        typedRoutesGeneration?: false | 'type' | 'runtime'
      }
    }

    react?: {
      /**
       * Enable the React Compiler, for all or specific platforms
       *
       * - `true` - enable for all platforms
       * - `'native'` - enable for iOS/Android only
       * - `'web'` - enable for web only
       * - `RegExp` - enable for files matching the pattern
       * - `(id: string) => boolean` - custom function to determine per-file
       * - `{ web, native }` - configure each platform separately
       *
       * @example
       * // enable for all
       * compiler: true
       *
       * @example
       * // different config per platform
       * compiler: {
       *   web: true,
       *   native: /\/components\//,
       * }
       *
       * @default false
       */
      compiler?: ReactCompilerConfig
    }

    optimization?: {
      /**
       * By default One scans your fs routes and adds them as Vite `entries`, this prevents some hard
       * reloads on web as you navigate to new pages, but can slow things startup.
       *
       * The 'flat' option is default and will automatically add just the routes at the root of your
       * app but nothing nested in non-group folders below that.
       *
       * @default 'flat'
       */
      autoEntriesScanning?: boolean | 'flat'
    }

    /**
     * Path to a js or ts file to import before the rest of your app runs
     * One controls your root, but you may want to runs some JS before anything else
     * Use this to give One the entrypoint to run
     *
     * Can be a string to use the same file for all environments, or an object to specify
     * different files per environment:
     *
     * @example
     * setupFile: './setup.ts'
     *
     * @example
     * setupFile: {
     *   client: './setup.client.ts',
     *   server: './setup.server.ts',
     *   native: './setup.native.ts'
     * }
     *
     * @example
     * setupFile: {
     *   client: './setup.client.ts',
     *   server: './setup.server.ts',
     *   ios: './setup.ios.ts',
     *   android: './setup.android.ts'
     * }
     */
    setupFile?:
      | string
      | {
          client?: string
          server?: string
          native?: string
        }
      | {
          client?: string
          server?: string
          ios?: string
          android?: string
        }

    config?: {
      ensureTSConfig?: false

      /**
       * One enables Vite's native resolve.tsconfigPaths by default.
       * Set this to false to disable tsconfig path resolution.
       *
       * Pass an object for fine-grained control (options are accepted for
       * backwards-compatibility; the built-in resolver always ignores config errors).
       *
       * @default true
       */
      tsConfigPaths?: boolean | { ignoreConfigErrors?: boolean }
    }

    /**
     * Set to `false` to completely disable native (iOS/Android) support.
     *
     * When disabled, One will not register the `ios`/`android` Vite environments,
     * skip the Metro / React Native dev server plugins, and skip all native-only
     * globals. Use this when you want to use One purely as a web framework.
     */
    native?:
      | false
      | ({
          /**
           * The uid of your native app, this will be used internally in one to call
           * `AppRegistry.registerComponent(key)`
           */
          key?: string

          /**
           * Wrap each route screen in a React Suspense boundary on native.
           *
           * On native the JS bundle already contains all route modules, so the
           * Suspense boundary only catches the async `import()` wrapper which
           * resolves on the next microtick. Setting this to `false` removes
           * the `<Suspense fallback={null}>` wrapper, which prevents a blank
           * flash when JS-driven navigation animations (e.g. SOI) slide a
           * screen in before the Suspense fallback is replaced.
           *
           * @default true
           */
          suspendRoutes?: boolean

          /**
           * Turns on react-native-css-interop support when importing CSS on native
           */
          css?: boolean

          /**
           * Specifies the bundler to use for native builds. Defaults to 'vite'.
           *
           * - 'metro' is recommended for production stability. Note that this option comes with some limitations, see https://onestack.dev/docs/metro-mode#limitations for more info.
           * - 'vite' is experimental but offers faster builds with SWC.
           *
           * Note that the ONE_METRO_MODE environment variable can override this setting to 'metro'.
           */
          bundler?: 'metro' | 'vite'
        } & (
          | {
              bundler: 'metro'
              /** Options merging for Metro is not fully implemented in the One plugin, changing this may not work properly. Search for "METRO-OPTIONS-MERGING" in the codebase for details. */
              bundlerOptions?: MetroPluginOptions
            }
          | {
              bundler?: 'vite'
              /** No configurable options with the default vite bundler. */
              bundlerOptions?: { currentlyHaveNoOptions?: null }
            }
        ))

    web?: {
      /**
       * Choose the default strategy for pages to be rendered on the web.
       *
       * For sites that are mostly static, choose "ssg":
       *   SSG stands for "server side generated", in this mode when you run `build`
       *   your pages will all be fully rendered on the server once during the build,
       *   outputting a static HTML page with the rendered page and loader data inlined.
       *   This gives better performance for initial render, and better SEO.
       *
       *
       * For apps that are mostly dynamic, choose "spa":
       *   SPA stands for "single page app", in this mode when you run `build` your
       *   pages will only render out an empty shell of HTML and will not attempt to
       *   server render at all. Loaders will be run on the server side and the data will
       *   be available to your app on initial render.
       *
       * @default 'ssg'
       */
      defaultRenderMode?: RouteRenderMode

      /**
       * An array of redirect objects, works in development and production:
       *
       * @example
       *
       * [
       *   {
       *     source: '/vite',
       *     destination: 'https://vxrn.dev',
       *     permanent: true,
       *   },
       *   {
       *     source: '/docs/components/:slug/:version',
       *     destination: '/ui/:slug/:version',
       *     permanent: true,
       *   },
       * ]
       *
       */
      redirects?: Redirect[]

      /**
       * Deployment target for production builds.
       *
       * String shorthand:
       * - 'node': Default Node.js server using Hono
       * - 'vercel': Vercel serverless functions
       * - 'cloudflare': Cloudflare Workers
       *
       * Object form allows additional configuration:
       * ```ts
       * deploy: {
       *   target: 'cloudflare',
       *   url: 'https://my-app.example.com',
       * }
       * ```
       *
       * @default 'node'
       */
      deploy?: DeployTarget | DeployConfig

      /**
       * @experimental
       * When true, inlines the CSS content directly into the HTML instead of using <link> tags.
       * This can improve performance by eliminating an extra network request for CSS.
       *
       * @default false
       */
      inlineLayoutCSS?: boolean

      /**
       * @experimental
       * Controls how scripts are loaded for improved performance.
       *
       * - `'defer-non-critical'`: Critical scripts (framework entry, page, layouts) load immediately.
       *   Non-critical scripts (component imports, utilities) become modulepreload hints only,
       *   reducing network/CPU contention.
       *
       * - `'after-lcp'`: Scripts download immediately via modulepreload but execution is deferred
       *   until after first paint using double requestAnimationFrame. This allows the browser to
       *   paint the SSR content before executing JavaScript. Only applies to SSG pages.
       *
       * - `'after-lcp-aggressive'`: Only modulepreloads critical scripts (entry, layouts).
       *   Non-critical scripts have no modulepreload hints, reducing network saturation.
       *   Best for pages with many chunks or slow networks.
       *
       * @default undefined (all scripts load with async)
       */
      experimental_scriptLoading?:
        | 'defer-non-critical'
        | 'after-lcp'
        | 'after-lcp-aggressive'

      /**
       * Generate a sitemap.xml file during build.
       *
       * Set to `true` for default behavior, or pass an object to configure:
       *
       * @example
       * sitemap: true
       *
       * @example
       * sitemap: {
       *   baseUrl: 'https://example.com',  // defaults to ONE_SERVER_URL env var
       *   priority: 0.7,                    // default priority for all routes
       *   changefreq: 'weekly',             // default changefreq for all routes
       *   exclude: ['/admin/*', '/api/*'],  // glob patterns to exclude
       * }
       *
       * Routes can also export sitemap metadata:
       * ```ts
       * export const sitemap = { priority: 0.9, changefreq: 'daily' }
       * // or exclude: export const sitemap = { exclude: true }
       * ```
       */
      sitemap?: boolean | SitemapOptions

      /**
       * Controls link prefetching strategy for improved navigation performance.
       *
       * - `'intent'`: Smart prefetching using mouse trajectory prediction (default)
       * - `'viewport'`: Prefetch links when they enter the viewport
       * - `'hover'`: Prefetch on mouseover only
       * - `false`: Disable prefetching entirely
       *
       * @default 'intent'
       */
      linkPrefetch?: false | 'hover' | 'viewport' | 'intent'

      /**
       * Skew protection detects when deployed assets have changed and handles
       * stale client bundles gracefully instead of showing broken pages.
       *
       * - `true` (default): Detects chunk load failures and auto-reloads
       * - `'proactive'`: Also polls for new versions and forces full-page
       *   navigation when a new deployment is detected
       * - `false`: Disable skew protection entirely
       *
       * @default true
       */
      skewProtection?: boolean | 'proactive'

      /**
       * Wrap each route screen in a React Suspense boundary on web.
       *
       * By default web does not wrap routes in Suspense (to avoid flickers
       * during navigation). Enable this if your routes use React `use()`
       * or lazy loading and you want Suspense boundaries per-route.
       *
       * @default false
       */
      suspendRoutes?: boolean
    }

    /**
     * Development tools configuration. Set to `true` to enable all devtools,
     * `false` to disable, or an object to configure individual tools.
     *
     * @default true
     */
    devtools?:
      | boolean
      | {
          /**
           * Enable the source inspector.
           * Hold Shift+Ctrl (or Shift+Cmd on Mac) and hover over elements
           * to see their source file location. Click to open in your editor.
           *
           * @default true (when devtools is enabled)
           */
          inspector?: boolean

          /**
           * Editor CLI command for the source inspector to open files with.
           * Examples: 'cursor', 'code', 'zed', 'subl', 'webstorm'
           *
           * Can also be set via the LAUNCH_EDITOR or EDITOR env var.
           */
          editor?: string

          /**
           * Enable the SEO preview panel.
           * Press Alt+S to toggle a panel showing how the page appears in
           * Google search results, Twitter cards, and Open Graph shares.
           *
           * @default true (when devtools is enabled)
           */
          seoPreview?: boolean
        }

    server?: VXRNOptions['server'] & {
      /**
       * Log HTTP requests to the console
       * @default true
       */
      loggingEnabled?: boolean
    }

    /**
     * Skip loading .env files during build
     */
    skipEnv?: boolean

    build?: {
      server?: VXRNBuildOptions | false
      api?: VXRNBuildOptions

      /**
       * Use worker threads for parallel static page generation during build.
       * This utilizes multiple CPU cores for faster builds.
       *
       * @default true
       */
      workers?: boolean

      /**
       * Scan client bundles for leaked secrets (API keys, tokens, passwords)
       * after building. Catches accidental exposure of server-side secrets
       * in client-facing JavaScript.
       *
       * - `'warn'` (default): Log warnings but don't fail the build
       * - `'error'`: Fail the build if secrets are found
       * - `false`: Disable scanning entirely
       *
       * Pass an object for fine-grained control:
       *
       * @example
       * securityScan: {
       *   level: 'error',
       *   safePatterns: [
       *     'sk_live_test_placeholder',
       *     /my-custom-uuid-[a-f0-9-]+/,
       *   ]
       * }
       *
       * For production deployments, we recommend setting this to `'error'`
       * to prevent secrets from being shipped to clients.
       *
       * @default 'warn'
       */
      securityScan?:
        | boolean
        | 'warn'
        | 'error'
        | {
            /**
             * @default 'warn'
             */
            level?: 'warn' | 'error'

            /**
             * Strings or regex patterns that should be considered safe
             * and ignored by the scanner. Useful for test tokens, known
             * UUIDs, or domain-specific patterns.
             */
            safePatterns?: (string | RegExp)[]
          }
    }

    patches?: FixDependencies

    ssr?: {
      /**
       * One scans dependencies on startup and decides which ones to optimize based on known broken
       * dependencies. These include react-native and react, which need to be optimized to work.
       * It finds all parent dependencies of the know bad deps and adds them to ssr.optimizeDeps.include.
       *
       * You can disable with false, or configure the include/exclude with options.
       *
       * Note: the **full path** (e.g. `<your_project_path>/node_modules/<some_package>`) will be used to match dependencies, if you are using a string to match a package name you may want to add `*` + `/` at the start and `/*` the end.
       *
       * @default { include: /node_modules/ }
       */
      autoDepsOptimization?: boolean | AutoDepOptimizationOptions

      /**
       * In monorepos with symlinked workspace packages, Vite's SSR optimizer registers
       * pre-bundled deps by their node_modules path, but the resolver follows symlinks
       * to the real (source) path. This causes duplicate module instances — the optimizer
       * serves one copy while source imports create another.
       *
       * Enable this to redirect symlink-resolved paths back to node_modules paths so
       * the optimizer can recognize and deduplicate them.
       *
       * @default false
       */
      dedupeSymlinkedModules?: boolean
    }

    /**
     * Configure environment guard behavior for `server-only`, `client-only`,
     * `native-only`, and `web-only` imports.
     *
     * Some libraries like `floating-ui` import `client-only` even though the
     * imported code works fine on the server. Use this to disable guards.
     *
     * @example
     * // disable the client-only guard (allows importing on server)
     * environmentGuards: { disableGuards: ['client-only'] }
     *
     * @example
     * // disable all environment guards
     * environmentGuards: { disabled: true }
     */
    environmentGuards?: EnvironmentGuardOptions
  }

  export interface RouteContext {
    keys(): string[]
    (id: string): any;
    <T>(id: string): T
    resolve(id: string): string
    id: string
  }

  export type Redirect = {
    source: string
    destination: string
    permanent: boolean
  }

  export type BuildInfo = {
    outDir?: string
    constants: {
      CACHE_KEY: string
    }
    oneOptions?: PluginOptions
    routeToBuildInfo: Record<string, Omit<One.RouteBuildInfo, 'loaderData'>>
    /** A mapping to lookup the full route name from a path */
    pathToRoute: Record<string, string>
    routeMap: Record<string, string>
    manifest: {
      pageRoutes: RouteInfo[]
      apiRoutes: RouteInfo[]
      allRoutes: RouteInfo[]
    }

    // for quick checking if preload exists
    preloads: Record<string, boolean>
    cssPreloads: Record<string, boolean>
    loaders: Record<string, boolean>
    // Whether the build used rolldown (affects API file naming)
    useRolldown?: boolean
  }

  export type AfterBuildProps = VXRNAfterBuildProps & BuildInfo

  export type RouteBuildInfo = {
    type: One.RouteType
    path: string
    routeFile: string
    middlewares: string[]
    preloadPath: string
    cssPreloadPath: string
    loaderPath: string
    cleanPath: string
    htmlPath: string
    clientJsPath: string
    serverJsPath: string
    params: object
    loaderData: any
    /** All preloads (for backwards compatibility) */
    preloads: string[]
    /** Critical preloads that load immediately with async */
    criticalPreloads?: string[]
    /** Non-critical preloads that are modulepreload hints only */
    deferredPreloads?: string[]
    css: string[]
    /** CSS from layout entries only - loaded before scripts to prevent FOUC */
    layoutCSS: string[]
    /** When inlineLayoutCSS is enabled, contains the actual CSS content */
    cssContents?: string[]
  }

  /**
   * Represents a matched route in the route hierarchy.
   * Used by useMatches() to expose loader data from all matched routes.
   */
  export type RouteMatch = {
    /** Route identifier (e.g., "docs/_layout" or "docs/intro") */
    routeId: string
    /** The pathname segment this route matched */
    pathname: string
    /** URL params extracted for this route */
    params: Record<string, string | string[]>
    /** Data returned from this route's loader */
    loaderData: unknown
  }

  export type ServerContext = {
    css?: string[]
    /** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
    cssContents?: string[]
    /** Number of inline CSS entries - used for hydration matching when cssContents is stripped */
    cssInlineCount?: number
    postRenderData?: any
    loaderData?: any
    loaderProps?: any
    mode?: 'spa' | 'ssg' | 'ssr' | 'spa-shell'
    // mapping of route keys to bundle paths for hydration preloading
    routePreloads?: Record<string, string>
    /**
     * All matched routes with their loader data.
     * Ordered from root layout to leaf page (parent → child).
     */
    matches?: RouteMatch[]
  }

  export type Flags = {}

  // Re-export from shared types so they're available from both 'one' and 'one/vite'
  export type SitemapChangefreq = OneShared.SitemapChangefreq
  export type RouteSitemap = OneShared.RouteSitemap

  export type SitemapOptions = {
    /**
     * Base URL for the sitemap. Defaults to ONE_SERVER_URL environment variable.
     */
    baseUrl?: string
    /**
     * Default priority for all routes (0.0 to 1.0).
     * @default 0.5
     */
    priority?: number
    /**
     * Default change frequency for all routes.
     */
    changefreq?: SitemapChangefreq
    /**
     * Glob patterns for routes to exclude from the sitemap.
     * API routes and not-found routes are always excluded.
     */
    exclude?: string[]
  }
}
