/**
 * Express analyzer
 * Extracts routes from Express applications
 */

export interface Route {
    method: string
    path: string
    description: string
    tags: string[]
    middleware: string[]
    parameters?: any[]
    responses: Record<string, any>
}

/**
 * Extract routes from Express app
 * Supports both Express 4.x and 5.x
 */
export function analyzeExpress(app: any): Route[] {
    const routes: Route[] = []
    const seenRoutes = new Set<string>() // Track duplicates

    // Express 4.x: Force router initialization (lazy-loaded)
    if (typeof app.lazyrouter === 'function') {
        app.lazyrouter()
    }

    // Get router stack - different locations in v4 vs v5
    let stack: any[] | null = null

    // Express 4.x: app._router.stack
    if (app._router && app._router.stack) {
        stack = app._router.stack
    }
    // Express 5.x: router property might be in different location or structure
    else if (app.router && app.router.stack) {
        stack = app.router.stack
    }
    // Express 5.x alternative: check layerStack
    else if (app.layerStack) {
        stack = app.layerStack
    }
    // Direct stack property
    else if (app.stack) {
        stack = app.stack
    }

    if (!stack || stack.length === 0) {
        console.warn('No routes found in Express app')
        console.log('📌 Tip: Ensure routes are registered before calling analyze()')
        console.log('📌 Express version compatibility: v4.x and v5.x supported')
        return routes
    }

    console.log(`Found ${stack.length} layers in Express router`)

    // Extract routes from Express router stack
    for (const layer of stack) {
        if (layer.route) {
            // Direct route
            const route = extractRoute(layer.route, '')
            if (route) {
                const key = `${route.method}:${route.path}`
                if (!seenRoutes.has(key)) {
                    routes.push(route)
                    seenRoutes.add(key)
                }
            }
        } else if (layer.name === 'router' && layer.handle.stack) {
            // Mounted router (e.g., app.use('/api', router))
            let basePath = layer.regexp.source
                // Remove regex escapes
                .replace(/\\\//g, '/')
                .replace(/\\/g, '')
                // Remove regex anchors
                .replace(/\^/g, '')
                .replace(/\$/g, '')
                // Remove optional markers
                .replace(/\?/g, '')
                // Remove regex lookahead/lookbehind patterns like (?=/|)
                .replace(/\([^)]*\)/g, '')
                || ''

            // Clean up basePath
            basePath = basePath.replace(/\/+/g, '/')
            if (basePath !== '/' && basePath.endsWith('/')) {
                basePath = basePath.slice(0, -1)
            }

            for (const subLayer of layer.handle.stack) {
                if (subLayer.route) {
                    const route = extractRoute(subLayer.route, basePath)
                    if (route) {
                        const key = `${route.method}:${route.path}`
                        if (!seenRoutes.has(key)) {
                            routes.push(route)
                            seenRoutes.add(key)
                        }
                    }
                }
            }
        }
    }

    return routes
}

function extractRoute(route: any, basePath: string): Route | null {
    if (!route.path) return null

    // Get all HTTP methods for this route
    const methods = Object.keys(route.methods)
        .filter(m => route.methods[m])
        .map(m => m.toUpperCase())

    if (methods.length === 0) return null

    // Clean up path - remove regex patterns and normalize
    let path = basePath + route.path

    // Remove regex escapes
    path = path.replace(/\\\//g, '/')

    // Remove regex lookahead patterns like (?=/|), (?:expr), etc.
    path = path.replace(/\(\?[^)]*\)/g, '')
    path = path.replace(/\([^:)][^)]*\)/g, '')

    // Remove optional regex markers
    path = path.replace(/\?/g, '')

    // Remove duplicate slashes
    path = path.replace(/\/+/g, '/')

    // Remove trailing slash (except for root)
    if (path !== '/' && path.endsWith('/')) {
        path = path.slice(0, -1)
    }

    // Ensure path starts with /
    if (!path.startsWith('/')) {
        path = '/' + path
    }

    // Extract middleware names
    const middleware: string[] = []
    if (route.stack) {
        for (const layer of route.stack) {
            if (layer.name && layer.name !== '<anonymous>' && layer.name !== 'bound dispatch') {
                middleware.push(layer.name)
            }
        }
    }

    // Extract path parameters
    const parameters: Route['parameters'] = []
    const paramMatches = path.match(/:(\w+)/g)
    if (paramMatches) {
        for (const match of paramMatches) {
            const paramName = match.slice(1) // Remove ':'
            parameters.push({
                name: paramName,
                in: 'path',
                required: true,
                type: 'string',
                description: `Path parameter: ${paramName}`,
            })
        }
    }

    // Create route object for primary method
    const method = methods[0] // Use first method (most common case)

    return {
        method,
        path,
        description: `${method} ${path}`,
        tags: [basePath ? basePath.split('/')[1] || 'default' : 'default'],
        middleware,
        parameters: parameters.length > 0 ? parameters : undefined,
        responses: {
            '200': {
                description: 'Successful response',
            },
        },
    }
}
