/**
 * Mizan @client decorator and function wrapper.
 *
 * Two registration styles:
 *
 * 1. Function wrapper (standalone functions):
 *    const userProfile = client({ context: UserCtx }, async (userId: number) => { ... })
 *
 * 2. Class decorator (methods):
 *    class Handlers {
 *      @client({ context: UserCtx })
 *      async userProfile(userId: number) { ... }
 *    }
 */

import { ReactContext, type ClientOptions, type RegistryEntry, type ParamDef } from './types'
import { register } from './registry'

function resolveContext(ctx: ReactContext | string | undefined): string | undefined {
    if (ctx instanceof ReactContext) return ctx.name
    return ctx
}

function normalizeAffects(
    affects: ClientOptions['affects'],
): RegistryEntry['affects'] | undefined {
    if (!affects) return undefined

    const items = Array.isArray(affects) ? affects : [affects]
    return items.map(item => {
        if (item instanceof ReactContext) {
            return { type: 'context' as const, name: item.name }
        }
        return { type: 'context' as const, name: item }
    })
}

function extractParams(fn: Function): ParamDef[] {
    // Extract parameter names from function.toString()
    const source = fn.toString()
    const match = source.match(/\(([^)]*)\)/)
    if (!match || !match[1].trim()) return []

    return match[1]
        .split(',')
        .map(p => p.trim())
        .filter(p => p && !p.startsWith('...'))
        .map(p => {
            // Handle destructured defaults: name = default, name: type
            const name = p.split(/[=:]/)[0].trim()
            return { name, type: 'any', required: !p.includes('=') }
        })
}

/**
 * Function wrapper — registers a standalone function.
 *
 * const userProfile = client({ context: UserCtx }, async (userId: number) => { ... })
 */
export function client<T extends (...args: any[]) => Promise<any>>(
    options: ClientOptions,
    fn: T,
): T

/**
 * Class method decorator.
 *
 * class Handlers {
 *   @client({ context: UserCtx })
 *   async userProfile(userId: number) { ... }
 * }
 */
export function client(options: ClientOptions): MethodDecorator

export function client(optionsOrFn: ClientOptions | ClientOptions, fn?: Function): any {
    // Function wrapper form: client(options, fn)
    if (fn && typeof fn === 'function') {
        const options = optionsOrFn as ClientOptions
        const context = resolveContext(options.context)
        const affects = normalizeAffects(options.affects)

        if (context && affects) {
            throw new Error('context and affects are mutually exclusive')
        }

        const name = fn.name || 'anonymous'
        const params = extractParams(fn)
        const isView = false // Determined at call time for function wrappers

        const entry: RegistryEntry = {
            name,
            fn: fn as any,
            context,
            affects,
            params,
            private: options.private ?? false,
            viewPath: isView,
            route: options.route,
            methods: options.methods,
            auth: options.auth,
            rev: options.rev,
            cache: options.cache,
        }

        register(entry)
        return fn
    }

    // Decorator form: @client(options)
    const options = optionsOrFn as ClientOptions
    return function (_target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const originalMethod = descriptor.value
        const context = resolveContext(options.context)
        const affects = normalizeAffects(options.affects)

        if (context && affects) {
            throw new Error('context and affects are mutually exclusive')
        }

        const params = extractParams(originalMethod)

        const entry: RegistryEntry = {
            name: propertyKey,
            fn: originalMethod,
            context,
            affects,
            params,
            private: options.private ?? false,
            viewPath: false,
            route: options.route,
            methods: options.methods,
            auth: options.auth,
            rev: options.rev,
            cache: options.cache,
        }

        register(entry)
        return descriptor
    }
}
