import {
	ScriptService,
	type MainArgSignature,
	FlowService,
	type Script,
	type AssetUsageAccessType
} from '$lib/gen'
import { get, writable } from 'svelte/store'
import type { Schema, SupportedLanguage } from './common.js'
import {
	type DynamicInput,
	emptySchema,
	getHubFlowIdFromPath,
	isHubFlowPath,
	sortObject
} from './utils.js'
import { tick } from 'svelte'

import initTsParser, { parse_deno, parse_outputs } from 'windmill-parser-wasm-ts'
import initRegexParsers, {
	parse_sql,
	parse_mysql,
	parse_oracledb,
	parse_duckdb,
	parse_bigquery,
	parse_snowflake,
	parse_graphql,
	parse_mssql,
	parse_db_resource,
	parse_bash,
	parse_powershell
} from 'windmill-parser-wasm-regex'
import initPythonParser, { parse_python } from 'windmill-parser-wasm-py'
import initAssetParser, {
	parse_assets_ts,
	parse_assets_py,
	parse_assets_sql
} from 'windmill-parser-wasm-asset'
import initGoParser, { parse_go } from 'windmill-parser-wasm-go'
import initPhpParser, { parse_php } from 'windmill-parser-wasm-php'
import initRustParser, { parse_rust } from 'windmill-parser-wasm-rust'
import initYamlParser, {
	parse_assets_ansible,
	parse_ansible,
	parse_ansible_delegate
} from 'windmill-parser-wasm-yaml'
import initCSharpParser, { parse_csharp } from 'windmill-parser-wasm-csharp'
import initNuParser, { parse_nu } from 'windmill-parser-wasm-nu'
import initJavaParser, { parse_java } from 'windmill-parser-wasm-java'
import initRubyParser, { parse_ruby } from 'windmill-parser-wasm-ruby'
import initRParser, { parse_r } from 'windmill-parser-wasm-r'

import wasmUrlTs from 'windmill-parser-wasm-ts/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRegex from 'windmill-parser-wasm-regex/windmill_parser_wasm_bg.wasm?url'
import wasmUrlPy from 'windmill-parser-wasm-py/windmill_parser_wasm_bg.wasm?url'
import wasmUrlGo from 'windmill-parser-wasm-go/windmill_parser_wasm_bg.wasm?url'
import wasmUrlPhp from 'windmill-parser-wasm-php/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRust from 'windmill-parser-wasm-rust/windmill_parser_wasm_bg.wasm?url'
import wasmUrlYaml from 'windmill-parser-wasm-yaml/windmill_parser_wasm_bg.wasm?url'
import wasmUrlCSharp from 'windmill-parser-wasm-csharp/windmill_parser_wasm_bg.wasm?url'
import wasmUrlNu from 'windmill-parser-wasm-nu/windmill_parser_wasm_bg.wasm?url'
import wasmUrlJava from 'windmill-parser-wasm-java/windmill_parser_wasm_bg.wasm?url'
import wasmUrlRuby from 'windmill-parser-wasm-ruby/windmill_parser_wasm_bg.wasm?url'
import wasmUrlR from 'windmill-parser-wasm-r/windmill_parser_wasm_bg.wasm?url'
import wasmUrlAsset from 'windmill-parser-wasm-asset/windmill_parser_wasm_bg.wasm?url'
import initWacParser, { parse_workflow_as_code } from 'windmill-parser-wasm-wac'
import wasmUrlWac from 'windmill-parser-wasm-wac/windmill_parser_wasm_bg.wasm?url'
import { workspaceStore } from './stores.js'
import { argSigToJsonSchemaType } from 'windmill-utils-internal'
import { type AssetWithAccessType } from './components/assets/lib.js'
import { type ColumnLineage } from './components/assets/AssetGraph/parsePipelineAnnotations'

const loadSchemaLastRun = writable<
	| [
			string | undefined,
			MainArgSignature | undefined,
			string | undefined,
			SupportedLanguage | 'bunnative' | undefined
	  ]
	| undefined
>(undefined)

// Memoize each WASM parser's init promise. Without this, concurrent callers
// (e.g. Promise.all in initFlowState across modules of the same language)
// would each invoke the initializer and race — if any one of them rejected
// transiently, the schema for that module would come back empty and the user
// had to modify the code to trigger a second inference.
function memoize(init: () => Promise<any>): () => Promise<any> {
	let promise: Promise<any> | undefined
	return () => {
		if (promise == undefined) {
			promise = init().catch((e) => {
				// Allow a subsequent call to retry after a failed init
				promise = undefined
				throw e
			})
		}
		return promise
	}
}

export const initWasmTs = memoize(() => initTsParser(wasmUrlTs))
const initWasmRegex = memoize(() => initRegexParsers(wasmUrlRegex))
const initWasmPython = memoize(() => initPythonParser(wasmUrlPy))
const initWasmPhp = memoize(() => initPhpParser(wasmUrlPhp))
const initWasmRust = memoize(() => initRustParser(wasmUrlRust))
const initWasmGo = memoize(() => initGoParser(wasmUrlGo))
const initWasmYaml = memoize(() => initYamlParser(wasmUrlYaml))
const initWasmCSharp = memoize(() => initCSharpParser(wasmUrlCSharp))
const initWasmNu = memoize(() => initNuParser(wasmUrlNu))
const initWasmJava = memoize(() => initJavaParser(wasmUrlJava))
const initWasmRuby = memoize(() => initRubyParser(wasmUrlRuby))
const initWasmR = memoize(() => initRParser(wasmUrlR))
const initWasmAsset = memoize(() => initAssetParser(wasmUrlAsset))
const initWasmWac = memoize(() => initWacParser(wasmUrlWac))

export type WacDagNode = {
	id: string
	node_type:
		| { type: 'Step'; name: string; script: string }
		| { type: 'InlineStep'; name: string }
		| { type: 'Sleep'; seconds: string }
		| { type: 'WaitForApproval' }
		| { type: 'Branch'; condition_source: string }
		| { type: 'ParallelStart' }
		| { type: 'ParallelEnd' }
		| { type: 'LoopStart'; iter_source: string }
		| { type: 'LoopEnd' }
		| { type: 'Merge' }
		| { type: 'Return' }
	label: string
	line: number
}

export type WacDagEdge = {
	from: string
	to: string
	label?: string
}

export type WacWorkflowDag = {
	nodes: WacDagNode[]
	edges: WacDagEdge[]
	params: { name: string; typ?: string }[]
	source_hash: string
}

export async function parseWacDag(
	code: string,
	language: string
): Promise<WacWorkflowDag | { errors: { message: string; line: number }[] } | null> {
	try {
		await initWasmWac()
		const raw = parse_workflow_as_code(code, language)
		const result = JSON.parse(raw)
		if (result.type === 'success') {
			return result as WacWorkflowDag
		} else if (result.type === 'error') {
			return { errors: result.errors }
		}
		return null
	} catch {
		return null
	}
}

type InferAssetsResult =
	| {
			status: 'ok'
			assets: AssetWithAccessType[]
			sql_queries?: InferAssetsSqlQueryDetails[]
			columns?: Record<string, AssetUsageAccessType>
			// Body-inferred column lineage (DuckDB SQL AST). Present once the
			// `windmill-parser-wasm-asset` package is rebuilt with the inference;
			// the spread below already forwards it from the parser output.
			column_lineage?: ColumnLineage[]
	  }
	| {
			status: 'error'
			error: string
			assets?: undefined
			sql_queries?: undefined
	  }

export type InferAssetsSqlQueryDetails = {
	query_string: string // SQL query with $1 placeholders for interpolations
	span: [number, number] // [start, end] byte positions in source code
	source_kind: 'datatable' | 'ducklake' // AssetKind equivalent
	source_name: string // e.g., "main", "dt"
	source_schema?: string // e.g., "public", optional
	has_raw_interpolation?: boolean // true if any ${sql.raw(...)} was used
	prepared?: PreparedAssetsSqlQuery
}

export type PreparedAssetsSqlQuery =
	| { columns: Record<string, string> } // e.g { id: "number", name: "text" }
	| { error: string; columns?: undefined } // error message if preparation failed

function parseVolumeAnnotations(code: string, commentPrefix: string): AssetWithAccessType[] {
	const volumes: AssetWithAccessType[] = []
	for (const line of code.split('\n')) {
		const trimmed = line.trim()
		if (!trimmed) continue
		if (!trimmed.startsWith(commentPrefix)) break
		const after = trimmed.slice(commentPrefix.length).trim()
		const match = after.match(/^volume:\s*(\S+)/)
		if (match) {
			volumes.push({ kind: 'volume', path: match[1], access_type: 'rw' })
		}
	}
	return volumes
}

function getCommentPrefix(language: SupportedLanguage | undefined): string | undefined {
	switch (language) {
		case 'python3':
		case 'bash':
		case 'powershell':
		case 'ansible':
		case 'ruby':
		case 'rlang':
			return '#'
		case 'deno':
		case 'bun':
		case 'bunnative':
		case 'nativets':
		case 'go':
			return '//'
		default:
			return undefined
	}
}

export async function inferAssets(
	language: SupportedLanguage | undefined,
	code: string
): Promise<InferAssetsResult> {
	function wrap(raw_result: string): InferAssetsResult {
		if (raw_result.startsWith('err:')) {
			return { status: 'error', error: raw_result.slice(4).trim() }
		}
		return { status: 'ok', ...JSON.parse(raw_result) }
	}

	let result: InferAssetsResult | undefined

	try {
		if (language === 'duckdb') {
			await initWasmAsset()
			result = wrap(parse_assets_sql(code))
		} else if (language === 'deno' || language === 'nativets' || language === 'bun') {
			await initWasmAsset()
			result = wrap(parse_assets_ts(code))
		} else if (language === 'python3') {
			await initWasmAsset()
			result = wrap(parse_assets_py(code))
		} else if (language === 'ansible') {
			await initWasmYaml()
			result = wrap(parse_assets_ansible(code))
		}
	} catch (e) {
		return { status: 'error', error: (e as Error)?.message || JSON.stringify(e) }
	}

	if (!result) {
		result = { status: 'ok', assets: [] }
	}

	const prefix = getCommentPrefix(language)
	if (prefix && result.status === 'ok') {
		const volumeAssets = parseVolumeAnnotations(code, prefix)
		if (volumeAssets.length > 0) {
			result = { ...result, assets: [...result.assets, ...volumeAssets] }
		}
	}

	return result
}

export async function inferAnsibleExecutionMode(code: string): Promise<any> {
	try {
		await initWasmYaml()
		return JSON.parse(parse_ansible_delegate(code))
	} catch (e) {
		console.error('error parsing git repo for ansible', e)
		return undefined
	}
}

const SQL_LANGUAGES = [
	'postgresql',
	'mysql',
	'bigquery',
	'snowflake',
	'mssql',
	'oracledb',
	'duckdb'
]

/**
 * Returns the parameter names of `entrypoint` in `code` (or `main` if not given),
 * or `undefined` if the function can't be found, the code can't be parsed, the
 * language isn't supported here, or the signature contains rest/keyword args
 * (in which case the callee should fall back to a conservative full comparison).
 *
 * Lighter than {@link inferArgs} — does not touch any schema.
 */
export async function parseEntrypointArgs(
	language: SupportedLanguage | 'bunnative' | undefined,
	code: string,
	entrypoint?: string
): Promise<Set<string> | undefined> {
	if (!code) return undefined
	try {
		let sig: MainArgSignature
		if (language === 'python3') {
			await initWasmPython()
			sig = JSON.parse(parse_python(code, entrypoint))
		} else if (
			language === 'deno' ||
			language === 'nativets' ||
			language === 'bun' ||
			language === 'bunnative'
		) {
			await initWasmTs()
			sig = JSON.parse(parse_deno(code, entrypoint))
		} else {
			return undefined
		}
		if (sig.type === 'Invalid') return undefined
		if (sig.star_args || sig.star_kwargs) return undefined
		if (!Array.isArray(sig.args)) return undefined
		// The parser sets auto_kind when no matching entrypoint function was
		// found — empty args in that case means "unknown signature", not
		// "function takes no params", so we fall back to a full comparison.
		if (sig.args.length === 0 && sig.auto_kind != null) return undefined
		return new Set(sig.args.map((a) => a.name))
	} catch {
		return undefined
	}
}

const helperEntrypointCache = new Map<string, Set<string> | undefined>()

/**
 * Resolves a {@link DynamicInput.HelperScript} to its entrypoint parameter
 * names. For deployed helpers it fetches the script (or the flow's inline
 * dyn-select code) once and caches the result per workspace+path+entrypoint.
 */
export async function getHelperEntrypointArgs(
	helper: DynamicInput.HelperScript,
	entrypoint?: string
): Promise<Set<string> | undefined> {
	if (helper.source === 'inline') {
		return parseEntrypointArgs(helper.lang, helper.code, entrypoint)
	}
	const workspace = get(workspaceStore)
	if (!workspace) return undefined
	const cacheKey = `${workspace}::${helper.runnable_kind}::${helper.path}::${entrypoint ?? ''}`
	if (helperEntrypointCache.has(cacheKey)) return helperEntrypointCache.get(cacheKey)
	let result: Set<string> | undefined
	try {
		if (helper.runnable_kind === 'script') {
			const script = await ScriptService.getScriptByPath({ workspace, path: helper.path })
			result = await parseEntrypointArgs(script.language, script.content ?? '', entrypoint)
		} else {
			const flow = await FlowService.getFlowByPath({ workspace, path: helper.path })
			const schema = flow.schema as Record<string, unknown> | undefined
			const code = schema?.['x-windmill-dyn-select-code']
			const lang = schema?.['x-windmill-dyn-select-lang']
			if (typeof code === 'string' && typeof lang === 'string') {
				result = await parseEntrypointArgs(lang as SupportedLanguage, code, entrypoint)
			}
		}
	} catch {
		result = undefined
	}
	helperEntrypointCache.set(cacheKey, result)
	return result
}

export async function inferArgs(
	language: SupportedLanguage | 'bunnative' | undefined,
	code: string,
	schema: Schema,
	mainOverride?: string
): Promise<{
	auto_kind: string | null
	has_preprocessor: boolean | null
} | null> {
	const lastRun = get(loadSchemaLastRun)
	let inferedSchema: MainArgSignature
	if (
		lastRun &&
		code == lastRun[0] &&
		lastRun[1] &&
		lastRun[2] == mainOverride &&
		lastRun[3] === language
	) {
		inferedSchema = lastRun[1]
	} else {
		if (code == '') {
			code = ' '
		}

		let inlineDBResource: string | undefined = undefined

		if (language && SQL_LANGUAGES.includes(language)) {
			await initWasmRegex()
		}

		if (
			['postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'oracledb'].includes(language ?? '')
		) {
			inlineDBResource = parse_db_resource(code)
		}

		if (language == 'python3') {
			await initWasmPython()
			inferedSchema = JSON.parse(parse_python(code, mainOverride))
		} else if (language == 'deno') {
			await initWasmTs()
			inferedSchema = JSON.parse(parse_deno(code, mainOverride))
		} else if (language == 'nativets') {
			await initWasmTs()
			inferedSchema = JSON.parse(parse_deno(code, mainOverride))
		} else if (language == 'bun' || language == 'bunnative') {
			await initWasmTs()
			inferedSchema = JSON.parse(parse_deno(code, mainOverride))
		} else if (language == 'postgresql') {
			inferedSchema = JSON.parse(parse_sql(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{
						name: 'database',
						typ: { resource: 'postgresql' }
					},
					...inferedSchema.args
				]
			}
		} else if (language == 'mysql') {
			inferedSchema = JSON.parse(parse_mysql(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{ name: 'database', typ: { resource: 'mysql' } },
					...inferedSchema.args
				]
			}
		} else if (language == 'bigquery') {
			inferedSchema = JSON.parse(parse_bigquery(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{ name: 'database', typ: { resource: 'bigquery' } },
					...inferedSchema.args
				]
			}
		} else if (language == 'oracledb') {
			inferedSchema = JSON.parse(parse_oracledb(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{ name: 'database', typ: { resource: 'oracledb' } },
					...inferedSchema.args
				]
			}
		} else if (language == 'duckdb') {
			inferedSchema = JSON.parse(parse_duckdb(code))
		} else if (language == 'snowflake') {
			inferedSchema = JSON.parse(parse_snowflake(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{ name: 'database', typ: { resource: 'snowflake' } },
					...inferedSchema.args
				]
			}
		} else if (language == 'mssql') {
			inferedSchema = JSON.parse(parse_mssql(code))
			if (inlineDBResource === undefined) {
				inferedSchema.args = [
					{ name: 'database', typ: { resource: 'ms_sql_server' } },
					...inferedSchema.args
				]
			}
		} else if (language == 'graphql') {
			await initWasmRegex()
			inferedSchema = JSON.parse(parse_graphql(code))
			inferedSchema.args = [{ name: 'api', typ: { resource: 'graphql' } }, ...inferedSchema.args]
		} else if (language == 'go') {
			await initWasmGo()
			inferedSchema = JSON.parse(parse_go(code))
		} else if (language == 'bash') {
			await initWasmRegex()
			inferedSchema = JSON.parse(parse_bash(code))
		} else if (language == 'powershell') {
			await initWasmRegex()
			inferedSchema = JSON.parse(parse_powershell(code))
		} else if (language == 'php') {
			await initWasmPhp()
			inferedSchema = JSON.parse(parse_php(code, mainOverride))
		} else if (language == 'rust') {
			await initWasmRust()
			inferedSchema = JSON.parse(parse_rust(code))
		} else if (language == 'ansible') {
			await initWasmYaml()
			inferedSchema = JSON.parse(parse_ansible(code))
		} else if (language == 'csharp') {
			await initWasmCSharp()
			inferedSchema = JSON.parse(parse_csharp(code))
		} else if (language == 'nu') {
			await initWasmNu()
			inferedSchema = JSON.parse(parse_nu(code))
		} else if (language == 'java') {
			await initWasmJava()
			inferedSchema = JSON.parse(parse_java(code))
		} else if (language == 'ruby') {
			await initWasmRuby()
			inferedSchema = JSON.parse(parse_ruby(code))
		} else if (language == 'rlang') {
			try {
				await initWasmR()
				inferedSchema = JSON.parse(parse_r(code))
			} catch {
				inferedSchema = parseRSignatureFallback(code)
			}
			// for related places search: ADD_NEW_LANG
		} else {
			return null
		}
		if (inferedSchema.type == 'Invalid') {
			throw new Error(inferedSchema.error)
		}
		loadSchemaLastRun.set([code, inferedSchema, mainOverride, language])
	}

	schema.required = []
	const oldProperties = JSON.parse(JSON.stringify(schema.properties))
	schema.properties = {}
	for (const arg of inferedSchema.args) {
		if (!(arg.name in oldProperties)) {
			schema.properties[arg.name] = { description: '', type: '' }
		} else {
			schema.properties[arg.name] = oldProperties[arg.name]
			if (schema.properties[arg.name].oneOf && !('oneof' in arg))
				delete schema.properties[arg.name].oneOf
		}
		schema.properties[arg.name] = sortObject(schema.properties[arg.name])

		argSigToJsonSchemaType(arg.typ, schema.properties[arg.name])

		// For T | T[] detection for debouncing arg accumulation
		if ((arg as any).otyp && (arg as any).otyp.includes('[') && (arg as any).otyp.includes('|')) {
			schema.properties[arg.name].originalType = (arg as any).otyp
		}

		schema.properties[arg.name].default = arg.default

		if (!arg.has_default && !schema.required.includes(arg.name)) {
			schema.required.push(arg.name)
		}
	}
	// Store PowerShell CmdletBinding metadata as schema extensions
	const psSchema = inferedSchema as MainArgSignature & {
		has_cmd_binding?: boolean
		supports_should_process?: boolean
	}
	if (language === 'powershell' && psSchema.has_cmd_binding) {
		;(schema as any)['x-windmill-ps-cmd-binding'] = true
		;(schema as any)['x-windmill-ps-supports-should-process'] =
			psSchema.supports_should_process ?? false
	} else {
		delete (schema as any)['x-windmill-ps-cmd-binding']
		delete (schema as any)['x-windmill-ps-supports-should-process']
	}

	await tick()

	return {
		auto_kind: inferedSchema.auto_kind,
		has_preprocessor: inferedSchema.has_preprocessor
	}
}

export async function loadSchemaFromPath(
	path: string,
	hash?: string,
	// The acting workspace when the flow editor runs in an AI session; else the nav workspace.
	workspace?: string
): Promise<Schema> {
	if (path.startsWith('hub/')) {
		const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })

		if (schema && typeof schema === 'object' && 'properties' in schema) {
			return schema as any
		} else {
			const newSchema = emptySchema()
			await inferArgs(language as SupportedLanguage, content ?? '', newSchema)
			return newSchema
		}
	} else if (hash) {
		const script = await ScriptService.getScriptByHash({
			workspace: workspace ?? get(workspaceStore)!,
			hash
		})

		return inferSchemaIfNecessary(script)
	} else {
		const script = await ScriptService.getScriptByPath({
			workspace: workspace ?? get(workspaceStore)!,
			path: path ?? ''
		})
		return inferSchemaIfNecessary(script)
	}
}

async function inferSchemaIfNecessary(script: Script) {
	if (script.schema) {
		return script.schema as any
	} else {
		const newSchema = emptySchema()
		await inferArgs(script.language, script.content ?? '', newSchema)
		return newSchema
	}
}

export async function loadSchema(
	workspace: string,
	path: string,
	runType: 'script' | 'flow' | 'hubscript'
): Promise<{ schema: Schema; summary: string | undefined }> {
	if (runType === 'script') {
		const script = await ScriptService.getScriptByPath({
			workspace,
			path
		})

		return { schema: script.schema as any, summary: script.summary }
	} else if (runType === 'flow') {
		if (isHubFlowPath(path)) {
			const hubFlowId = getHubFlowIdFromPath(path)
			if (hubFlowId === undefined) {
				throw new Error(`Invalid hub flow path: ${path}`)
			}
			const hub = await FlowService.getHubFlowById({ id: hubFlowId })
			const flow = hub.flow
			const schema =
				flow?.schema && typeof flow.schema === 'object' && Object.keys(flow.schema).length > 0
					? (flow.schema as any)
					: emptySchema()
			return { schema, summary: flow?.summary }
		}

		const flow = await FlowService.getFlowByPath({
			workspace,
			path
		})

		return { schema: flow.schema as any, summary: flow.summary }
	} else {
		const script = await ScriptService.getHubScriptByPath({
			path
		})
		if (
			script.schema == undefined ||
			Object.keys(script.schema).length == 0 ||
			typeof script.schema != 'object'
		) {
			script.schema = emptySchema()
		}

		await inferArgs(script.language as SupportedLanguage, script.content, script.schema as any)
		return { schema: script.schema as any, summary: script.summary }
	}
}

export async function parseOutputs(
	code: string,
	ignoreError
): Promise<[string, string][] | undefined> {
	await initWasmTs()
	const getOutputs = await parse_outputs(code)
	const outputs = JSON.parse(getOutputs)
	if (outputs.error) {
		if (ignoreError) {
			return undefined
		}
		throw new Error(outputs.error)
	}
	return outputs.error ? [] : outputs.outputs
}

/** JS fallback parser for R main() signatures when WASM parser is unavailable. */
function parseRSignatureFallback(code: string): MainArgSignature {
	const result: MainArgSignature = {
		type: 'Valid',
		error: '',
		star_args: false,
		star_kwargs: false,
		args: [],
		has_preprocessor: null,
		auto_kind: null
	}

	const mainMatch = code.match(/\bmain\s*(?:<-|=)\s*function\s*\(([^)]*)\)/)
	if (!mainMatch) {
		return result
	}

	const paramsStr = mainMatch[1].trim()
	if (!paramsStr) return result

	// Split params respecting nested parens
	const params: string[] = []
	let depth = 0
	let current = ''
	for (const ch of paramsStr) {
		if ('([{'.includes(ch)) {
			depth++
			current += ch
		} else if (')]}'.includes(ch)) {
			depth--
			current += ch
		} else if (ch === ',' && depth === 0) {
			params.push(current)
			current = ''
		} else {
			current += ch
		}
	}
	if (current.trim()) params.push(current)

	for (const param of params) {
		const trimmed = param.trim()
		if (!trimmed) continue

		const eqIndex = trimmed.indexOf('=')
		if (eqIndex === -1) {
			result.args.push({ name: trimmed, typ: 'unknown', has_default: false, default: undefined })
		} else {
			const name = trimmed.slice(0, eqIndex).trim()
			const raw = trimmed.slice(eqIndex + 1).trim()
			const parsed = parseRDefault(raw)
			result.args.push({ name, typ: parsed.typ, has_default: true, default: parsed.value })
		}
	}

	return result
}

function parseRDefault(raw: string): { value: unknown; typ: MainArgSignature['args'][0]['typ'] } {
	if (raw === 'TRUE' || raw === 'true') return { value: true, typ: 'bool' }
	if (raw === 'FALSE' || raw === 'false') return { value: false, typ: 'bool' }
	if (raw === 'NULL') return { value: null, typ: 'unknown' }
	if (/^-?\d+(\.\d+)?$/.test(raw)) {
		const num = Number(raw)
		if (Number.isInteger(num) && !raw.includes('.')) return { value: num, typ: 'int' }
		return { value: num, typ: 'float' }
	}
	const strMatch = raw.match(/^"((?:[^"\\]|\\.)*)"$/) || raw.match(/^'((?:[^'\\]|\\.)*)'$/)
	if (strMatch) return { value: strMatch[1], typ: { str: null } }
	if (raw.startsWith('list(') || raw.startsWith('c(')) return { value: null, typ: { list: null } }
	return { value: null, typ: 'unknown' }
}
