package ssa

import (
	"debug/dwarf"
	"fmt"
	"go/token"
	"go/types"
	"path/filepath"
	"unsafe"

	"github.com/goplus/llvm"
)

type Positioner interface {
	Position(pos token.Pos) token.Position
}

type aDIBuilder struct {
	di         *llvm.DIBuilder
	prog       Program
	types      map[Type]DIType
	positioner Positioner
	m          llvm.Module // Add this field
}

type diBuilder = *aDIBuilder

func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder {
	m := pkg.mod
	ctx := m.Context()

	b := &aDIBuilder{
		di:         llvm.NewDIBuilder(m),
		prog:       prog,
		types:      make(map[*aType]DIType),
		positioner: positioner,
		m:          m, // Initialize the m field
	}

	b.addNamedMetadataOperand("llvm.module.flags", 2, "Debug Info Version", 3)
	b.addNamedMetadataOperand("llvm.module.flags", 7, "Dwarf Version", 4)
	b.addNamedMetadataOperand("llvm.module.flags", 1, "wchar_size", 4)
	b.addNamedMetadataOperand("llvm.module.flags", 8, "PIC Level", 2)
	b.addNamedMetadataOperand("llvm.module.flags", 7, "uwtable", 1)
	b.addNamedMetadataOperand("llvm.module.flags", 7, "frame-pointer", 1)

	// Add llvm.ident metadata
	identNode := ctx.MDNode([]llvm.Metadata{
		ctx.MDString("LLGo Compiler"),
	})
	m.AddNamedMetadataOperand("llvm.ident", identNode)

	return b
}

// New method to add named metadata operand
func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) {
	ctx := b.m.Context()
	b.m.AddNamedMetadataOperand(name,
		ctx.MDNode([]llvm.Metadata{
			llvm.ConstInt(ctx.Int32Type(), uint64(intValue), false).ConstantAsMetadata(),
			ctx.MDString(stringValue),
			llvm.ConstInt(ctx.Int32Type(), uint64(intValue2), false).ConstantAsMetadata(),
		}),
	)
}

// ----------------------------------------------------------------------------

type aCompilationUnit struct {
	ll llvm.Metadata
}

type CompilationUnit = *aCompilationUnit

func (c CompilationUnit) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
	return &aDIScopeMeta{c.ll}
}

var DWARF_LANG_C llvm.DwarfLang = 0x2
var DWARF_LANG_GO llvm.DwarfLang = 0x16

func (b diBuilder) createCompileUnit(filename, dir string) CompilationUnit {
	return &aCompilationUnit{ll: b.di.CreateCompileUnit(llvm.DICompileUnit{
		// TODO(lijie): use C language for now, change after Go plugin of LLDB is ready
		Language:       DWARF_LANG_C - 1,
		File:           filename,
		Dir:            dir,
		Producer:       "LLGo",
		Optimized:      true,
		RuntimeVersion: 1,
	})}
}

// ----------------------------------------------------------------------------

type aDIScopeMeta struct {
	ll llvm.Metadata
}

type DIScopeMeta = *aDIScopeMeta

type DIScope interface {
	scopeMeta(b diBuilder, pos token.Position) DIScopeMeta
}

// ----------------------------------------------------------------------------

type aDIFile struct {
	ll llvm.Metadata
}

type DIFile = *aDIFile

func (b diBuilder) createFile(filename string) DIFile {
	dir, file := filepath.Split(filename)
	return &aDIFile{ll: b.di.CreateFile(file, dir)}
}

func (f DIFile) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
	return &aDIScopeMeta{b.file(pos.Filename).ll}
}

// ----------------------------------------------------------------------------

type aDIType struct {
	ll llvm.Metadata
}

type DIType = *aDIType

func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType {
	var typ llvm.Metadata
	switch t := ty.RawType().(type) {
	case *types.Basic:
		if t.Kind() == types.UnsafePointer {
			typ = b.di.CreatePointerType(llvm.DIPointerType{
				Name:        name,
				SizeInBits:  b.prog.SizeOf(b.prog.rawType(t)) * 8,
				AlignInBits: uint32(b.prog.sizes.Alignof(t) * 8),
			})
			return &aDIType{typ}
		}

		var encoding llvm.DwarfTypeEncoding
		if t.Info()&types.IsBoolean != 0 {
			encoding = llvm.DW_ATE_boolean
		} else if t.Info()&types.IsUnsigned != 0 {
			encoding = llvm.DW_ATE_unsigned
		} else if t.Info()&types.IsInteger != 0 {
			encoding = llvm.DW_ATE_signed
		} else if t.Info()&types.IsFloat != 0 {
			encoding = llvm.DW_ATE_float
		} else if t.Info()&types.IsComplex != 0 {
			return b.createComplexType(ty)
		} else if t.Info()&types.IsString != 0 {
			return b.createStringType()
		} else {
			panic(fmt.Errorf("can't create debug info of basic type: %v, %T", ty.RawType(), ty.RawType()))
		}

		typ = b.di.CreateBasicType(llvm.DIBasicType{
			Name:       name,
			SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8,
			Encoding:   encoding,
		})
	case *types.Pointer:
		return b.createPointerType(name, b.prog.rawType(t.Elem()), pos)
	case *types.Named:
		// Create typedef type for named types
		return b.createTypedefType(name, ty, pos)
	case *types.Interface:
		ty := b.prog.rtType("Iface")
		return b.createInterfaceType(name, ty)
	case *types.Slice:
		ty := b.prog.rtType("Slice")
		tyElem := b.prog.rawType(t.Elem())
		return b.createSliceType(name, ty, tyElem)
	case *types.Struct:
		return b.createStructType(name, ty, pos)
	case *types.Signature:
		tyFn := b.prog.Closure(t)
		return b.createFuncPtrType(name, tyFn, pos)
	case *types.Array:
		return b.createArrayType(ty, t.Len())
	case *types.Chan:
		return b.createChanType(name, ty, pos)
	case *types.Map:
		ty := b.prog.rtType("Map")
		return b.createMapType(name, ty, pos)
	case *types.Tuple:
		return b.createTupleType(name, ty, pos)
	default:
		panic(fmt.Errorf("can't create debug info of type: %v, %T", ty.RawType(), ty.RawType()))
	}
	return &aDIType{typ}
}

// ----------------------------------------------------------------------------

type aDIFunction struct {
	ll llvm.Metadata
}

type DIFunction = *aDIFunction

func (p Function) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
	return &aDIScopeMeta{p.diFunc.ll}
}

// ----------------------------------------------------------------------------

type aDIGlobalVariableExpression struct {
	ll llvm.Metadata
}

type DIGlobalVariableExpression = *aDIGlobalVariableExpression

func (b diBuilder) createGlobalVariableExpression(scope DIScope, pos token.Position, name, linkageName string, ty Type, isLocalToUnit bool) DIGlobalVariableExpression {
	return &aDIGlobalVariableExpression{
		ll: b.di.CreateGlobalVariableExpression(
			scope.scopeMeta(b, pos).ll,
			llvm.DIGlobalVariableExpression{
				Name:        name,
				LinkageName: linkageName,
				File:        b.file(pos.Filename).ll,
				Line:        pos.Line,
				Type:        b.diType(ty, pos).ll,
				LocalToUnit: isLocalToUnit,
				AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
			},
		),
	}
}

// ----------------------------------------------------------------------------

type aDILexicalBlock struct {
	ll llvm.Metadata
}

type DILexicalBlock = *aDILexicalBlock

func (l *aDILexicalBlock) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
	return &aDIScopeMeta{l.ll}
}

// ----------------------------------------------------------------------------

type aDIVar struct {
	ll llvm.Metadata
}

type DIVar = *aDIVar

func (b diBuilder) createParameterVariable(scope DIScope, pos token.Position, name string, argNo int, ty DIType) DIVar {
	return &aDIVar{
		ll: b.di.CreateParameterVariable(
			scope.scopeMeta(b, pos).ll,
			llvm.DIParameterVariable{
				Name:           name,
				File:           b.file(pos.Filename).ll,
				Line:           pos.Line,
				ArgNo:          argNo,
				Type:           ty.ll,
				AlwaysPreserve: true,
			},
		),
	}
}

func (b diBuilder) createAutoVariable(scope DIScope, pos token.Position, name string, ty DIType) DIVar {
	return &aDIVar{
		ll: b.di.CreateAutoVariable(
			scope.scopeMeta(b, pos).ll,
			llvm.DIAutoVariable{
				Name:           name,
				File:           b.file(pos.Filename).ll,
				Line:           pos.Line,
				Type:           ty.ll,
				AlwaysPreserve: true,
			},
		),
	}
}

func (b diBuilder) createTypedefType(name string, ty Type, pos token.Position) DIType {
	underlyingType := b.diType(b.prog.rawType(ty.RawType().(*types.Named).Underlying()), pos)
	typ := b.di.CreateTypedef(llvm.DITypedef{
		Name:        name,
		Type:        underlyingType.ll,
		File:        b.file(pos.Filename).ll,
		Line:        pos.Line,
		AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
	})
	return &aDIType{typ}
}

func (b diBuilder) createStringType() DIType {
	ty := b.prog.rtType("String")
	return b.doCreateStructType("string", ty, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{
			b.createMemberType("data", ty, b.prog.CStr(), 0),
			b.createMemberType("len", ty, b.prog.Uint(), 1),
		}
	})
}

func (b diBuilder) createArrayType(ty Type, l int64) DIType {
	tyElem := b.prog.rawType(ty.RawType().(*types.Array).Elem())
	return &aDIType{ll: b.di.CreateArrayType(llvm.DIArrayType{
		SizeInBits:  b.prog.SizeOf(ty) * 8,
		AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
		ElementType: b.diType(tyElem, token.Position{}).ll,
		Subscripts: []llvm.DISubrange{{
			Count: l,
		}},
	})}
}

func (b diBuilder) createSliceType(name string, ty, tyElem Type) DIType {
	pos := token.Position{}
	diElemTyPtr := b.prog.Pointer(tyElem)

	return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{
			b.createMemberTypeEx("data", ty, diElemTyPtr, 0, pos, 0),
			b.createMemberTypeEx("len", ty, b.prog.Uint(), 1, pos, 0),
			b.createMemberTypeEx("cap", ty, b.prog.Uint(), 2, pos, 0),
		}
	})
}

func (b diBuilder) createInterfaceType(name string, ty Type) DIType {
	tyRaw := ty.RawType().Underlying()
	tyIntr := b.prog.rawType(tyRaw)
	tyType := b.prog.VoidPtr()
	tyData := b.prog.VoidPtr()

	return b.doCreateStructType(name, tyIntr, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{
			b.createMemberType("type", ty, tyType, 0),
			b.createMemberType("data", ty, tyData, 1),
		}
	})
}

func (b diBuilder) createMemberType(name string, tyStruct, tyField Type, idxField int) llvm.Metadata {
	return b.createMemberTypeEx(name, tyStruct, tyField, idxField, token.Position{}, 0)
}

func (b diBuilder) createMemberTypeEx(name string, tyStruct, tyField Type, idxField int, pos token.Position, flags int) llvm.Metadata {
	return b.di.CreateMemberType(
		b.diType(tyStruct, pos).ll,
		llvm.DIMemberType{
			Name:         name,
			SizeInBits:   b.prog.SizeOf(tyField) * 8,
			AlignInBits:  uint32(b.prog.sizes.Alignof(tyField.RawType()) * 8),
			OffsetInBits: b.prog.OffsetOf(tyStruct, idxField) * 8,
			Type:         b.diType(tyField, pos).ll,
			Flags:        flags,
		},
	)
}

func (b diBuilder) createMapType(name string, tyMap Type, pos token.Position) DIType {
	// ty := tyMap.RawType().(*types.Map)
	// tk := b.prog.rawType(ty.Key())
	// tv := b.prog.rawType(ty.Elem())
	tyCount := b.prog.Int()
	return b.doCreateStructType(name, tyMap, pos, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{
			b.createMemberType("count", tyMap, tyCount, 0),
		}
	})
}

func (b diBuilder) createChanType(name string, t Type, pos token.Position) DIType {
	return b.doCreateStructType(name, t, pos, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{}
	})
}

func (b diBuilder) createComplexType(t Type) DIType {
	var tfield Type
	var tyName string
	if t.RawType().(*types.Basic).Kind() == types.Complex128 {
		tfield = b.prog.Float64()
		tyName = "complex128"
	} else {
		tfield = b.prog.Float32()
		tyName = "complex64"
	}
	return b.doCreateStructType(tyName, t, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
		return []llvm.Metadata{
			b.createMemberType("real", t, tfield, 0),
			b.createMemberType("imag", t, tfield, 1),
		}
	})
}

func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) DIType {
	ptrType := b.prog.VoidPtr()
	return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{
		Name:        name,
		Pointee:     b.diType(ty, pos).ll,
		SizeInBits:  b.prog.SizeOf(ptrType) * 8,
		AlignInBits: uint32(b.prog.sizes.Alignof(ptrType.RawType())) * 8,
	})}
}

func (b diBuilder) doCreateStructType(name string, ty Type, pos token.Position, fn func(ty DIType) []llvm.Metadata) (ret DIType) {
	structType := ty.RawType().Underlying()

	scope := b.file(pos.Filename)
	ret = &aDIType{b.di.CreateReplaceableCompositeType(
		scope.ll,
		llvm.DIReplaceableCompositeType{
			Tag:         dwarf.TagStructType,
			Name:        name,
			File:        b.file(pos.Filename).ll,
			Line:        pos.Line,
			SizeInBits:  b.prog.SizeOf(ty) * 8,
			AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8),
		},
	)}
	b.types[ty] = ret

	fields := fn(ret)

	st := b.di.CreateStructType(
		scope.ll,
		llvm.DIStructType{
			Name:        name,
			File:        b.file(pos.Filename).ll,
			Line:        pos.Line,
			SizeInBits:  b.prog.SizeOf(ty) * 8,
			AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8),
			Elements:    fields,
		},
	)
	ret.ll.ReplaceAllUsesWith(st)
	ret.ll = st
	return
}

func (b diBuilder) createStructType(name string, ty Type, pos token.Position) (ret DIType) {
	structType := ty.RawType().(*types.Struct)
	return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
		fields := make([]llvm.Metadata, structType.NumFields())
		for i := 0; i < structType.NumFields(); i++ {
			field := structType.Field(i)
			tyField := b.prog.rawType(field.Type())
			flags := 0
			pos := b.positioner.Position(field.Pos())
			fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, flags)
		}
		return fields
	})
}

func (b diBuilder) createTupleType(name string, ty Type, pos token.Position) DIType {
	tupleType := ty.RawType().(*types.Tuple)
	if tupleType.Len() == 0 {
		return &aDIType{}
	}
	if tupleType.Len() == 1 {
		t := b.prog.rawType(tupleType.At(0).Type())
		return b.diType(t, pos)
	}
	return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
		fields := make([]llvm.Metadata, ty.RawType().(*types.Tuple).Len())
		for i := 0; i < ty.RawType().(*types.Tuple).Len(); i++ {
			field := ty.RawType().(*types.Tuple).At(i)
			tyField := b.prog.rawType(field.Type())
			fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, 0)
		}
		return fields
	})
}

func (b diBuilder) createFuncPtrType(name string, ty Type, pos token.Position) DIType {
	ptr := b.prog.VoidPtr()
	return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{
		Name:        name,
		Pointee:     b.diType(ptr, pos).ll,
		SizeInBits:  b.prog.SizeOf(ptr) * 8,
		AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8),
	})}
}

// ----------------------------------------------------------------------------

func (b diBuilder) dbgDeclare(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) {
	loc := llvm.DebugLoc{
		Line:  uint(pos.Line),
		Col:   uint(pos.Column),
		Scope: scope.scopeMeta(b, pos).ll,
	}
	b.di.InsertDeclareAtEnd(
		v.impl,
		dv.ll,
		expr.ll,
		loc,
		blk.last,
	)
}

func (b diBuilder) dbgValue(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) {
	loc := llvm.DebugLoc{
		Line:  uint(pos.Line),
		Col:   uint(pos.Column),
		Scope: scope.scopeMeta(b, pos).ll,
	}
	b.di.InsertValueAtEnd(
		v.impl,
		dv.ll,
		expr.ll,
		loc,
		blk.last,
	)
}

func (b diBuilder) diType(t Type, pos token.Position) DIType {
	name := t.RawType().String()
	return b.diTypeEx(name, t, pos)
}

func (b diBuilder) diTypeEx(name string, t Type, pos token.Position) DIType {
	if ty, ok := b.types[t]; ok {
		return ty
	}
	ty := b.createType(name, t, pos)
	b.types[t] = ty
	return ty
}

func (b diBuilder) varParam(scope DIScope, pos token.Position, varName string, vt DIType, argNo int) DIVar {
	return b.createParameterVariable(
		scope,
		pos,
		varName,
		argNo,
		vt,
	)
}

func (b diBuilder) varAuto(scope DIScope, pos token.Position, varName string, vt DIType) DIVar {
	return b.createAutoVariable(scope, pos, varName, vt)
}

func (b diBuilder) file(filename string) DIFile {
	return b.createFile(filename)
}

// ----------------------------------------------------------------------------

type aDIExpression struct {
	ll llvm.Metadata
}

type DIExpression = *aDIExpression

func (b diBuilder) createExpression(ops []uint64) DIExpression {
	return &aDIExpression{b.di.CreateExpression(ops)}
}

// -----------------------------------------------------------------------------

// Copy to alloca'd memory to get declareable address.
func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) {
	if v, ok := b.dbgVars[v]; ok {
		return v.ptr, v.val, true
	}
	t := v.Type.RawType().Underlying()
	dbgPtr, dbgVal = b.doConstructDebugAddr(v, t)
	dbgExpr := dbgExpr{dbgPtr, dbgVal}
	b.dbgVars[v] = dbgExpr
	b.dbgVars[dbgVal] = dbgExpr
	return dbgPtr, dbgVal, false
}

func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal Expr) {
	var ty Type
	switch t := t.(type) {
	case *types.Basic:
		if t.Info()&types.IsComplex != 0 {
			if t.Kind() == types.Complex128 {
				ty = b.Prog.Complex128()
			} else {
				ty = b.Prog.Complex64()
			}
		} else if t.Info()&types.IsString != 0 {
			ty = b.Prog.rtType("String")
		} else {
			ty = v.Type
		}
	case *types.Struct:
		ty = v.Type
	case *types.Slice:
		ty = b.Prog.Type(b.Prog.rtType("Slice").RawType().Underlying(), InGo)
	case *types.Signature:
		ty = b.Prog.Closure(t)
	case *types.Named:
		ty = b.Prog.Type(t.Underlying(), InGo)
	case *types.Map:
		ty = b.Prog.Type(b.Prog.rtType("Map").RawType().Underlying(), InGo)
	default:
		ty = v.Type
	}
	dbgPtr = b.AllocaT(ty)
	dbgPtr.Type = b.Prog.Pointer(v.Type)
	b.Store(dbgPtr, v)
	dbgVal = b.Load(dbgPtr)
	return dbgPtr, dbgVal
}

func (b Builder) di() diBuilder {
	return b.Pkg.di
}

func (b Builder) DIParam(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
	b.DIValue(variable, v, dv, scope, pos, blk)
}

func (b Builder) DIDeclare(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
	expr := b.di().createExpression(nil)
	b.di().dbgDeclare(v, dv, scope, pos, expr, blk)
}

func (b Builder) DIValue(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
	ty := v.Type.RawType().Underlying()
	if !needConstructAddr(ty) {
		expr := b.di().createExpression(nil)
		b.di().dbgValue(v, dv, scope, pos, expr, blk)
	} else {
		dbgPtr, _, _ := b.constructDebugAddr(v)
		expr := b.di().createExpression([]uint64{opDeref})
		b.di().dbgValue(dbgPtr, dv, scope, pos, expr, blk)
	}
}

const (
	opDeref = 0x06
)

func needConstructAddr(t types.Type) bool {
	switch t := t.(type) {
	case *types.Basic:
		if t.Info()&types.IsComplex != 0 {
			return true
		} else if t.Info()&types.IsString != 0 {
			return true
		}
		return false
	case *types.Pointer:
		return false
	default:
		return true
	}
}

func (b Builder) DIVarParam(scope DIScope, pos token.Position, varName string, vt Type, argNo int) DIVar {
	t := b.di().diType(vt, pos)
	return b.di().varParam(scope, pos, varName, t, argNo)
}

func (b Builder) DIVarAuto(scope DIScope, pos token.Position, varName string, vt Type) DIVar {
	t := b.di().diType(vt, pos)
	return b.di().varAuto(scope, pos, varName, t)
}

// hack for types.Scope
type hackScope struct {
	parent   *types.Scope
	children []*types.Scope
	number   int                     // parent.children[number-1] is this scope; 0 if there is no parent
	elems    map[string]types.Object // lazily allocated
	pos, end token.Pos               // scope extent; may be invalid
	comment  string                  // for debugging only
	isFunc   bool                    // set if this is a function scope (internal use only)
}

func isFunc(scope *types.Scope) bool {
	hs := (*hackScope)(unsafe.Pointer(scope))
	return hs.isFunc
}

func (b Builder) DIScope(f Function, scope *types.Scope) DIScope {
	if cachedScope, ok := b.diScopeCache[scope]; ok {
		return cachedScope
	}
	pos := b.di().positioner.Position(scope.Pos())
	// skip package and universe scope
	// if scope.Parent().Parent() == nil {
	// 	return b.di().file(pos.Filename)
	// }

	var result DIScope
	if isFunc(scope) {
		// TODO(lijie): should check scope == function scope
		result = f
	} else {
		parentScope := b.DIScope(f, scope.Parent())
		result = &aDILexicalBlock{b.di().di.CreateLexicalBlock(parentScope.scopeMeta(b.di(), pos).ll, llvm.DILexicalBlock{
			File:   b.di().file(pos.Filename).ll,
			Line:   pos.Line,
			Column: pos.Column,
		})}
	}

	b.diScopeCache[scope] = result
	return result
}

const (
	MD_dbg = 0
)

func (b Builder) DIGlobal(v Expr, name string, pos token.Position) {
	if _, ok := b.Pkg.glbDbgVars[v]; ok {
		return
	}
	gv := b.di().createGlobalVariableExpression(
		b.Pkg.cu,
		pos,
		name,
		name,
		v.Type,
		false,
	)
	v.impl.AddMetadata(MD_dbg, gv.ll)
	b.Pkg.glbDbgVars[v] = true
}

func (b Builder) DISetCurrentDebugLocation(diScope DIScope, pos token.Position) {
	b.impl.SetCurrentDebugLocation(
		uint(pos.Line),
		uint(pos.Column),
		diScope.scopeMeta(b.di(), pos).ll,
		llvm.Metadata{},
	)
}

func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Position) {
	p := f
	if p.diFunc == nil {
		sig := p.Type.raw.Type.(*types.Signature)
		rt := p.Prog.Type(sig.Results(), InGo)
		paramTypes := make([]llvm.Metadata, len(p.params)+1)
		paramTypes[0] = b.di().diType(rt, pos).ll
		for i, t := range p.params {
			paramTypes[i+1] = b.di().diType(t, pos).ll
		}
		diFuncType := b.di().di.CreateSubroutineType(llvm.DISubroutineType{
			File:       b.di().file(pos.Filename).ll,
			Parameters: paramTypes,
		})
		dif := llvm.DIFunction{
			Type:         diFuncType,
			Name:         p.Name(),
			LinkageName:  p.Name(),
			File:         b.di().file(pos.Filename).ll,
			Line:         pos.Line,
			ScopeLine:    bodyPos.Line,
			IsDefinition: true,
			LocalToUnit:  true,
			Optimized:    true,
		}
		p.diFunc = &aDIFunction{
			b.di().di.CreateFunction(b.di().file(pos.Filename).ll, dif),
		}
		p.impl.SetSubprogram(p.diFunc.ll)
	}
	b.impl.SetCurrentDebugLocation(
		uint(bodyPos.Line),
		uint(bodyPos.Column),
		p.diFunc.ll,
		f.impl.InstructionDebugLoc(),
	)
}

func (b Builder) Param(idx int) Expr {
	p := b.Func.Param(idx)
	if v, ok := b.dbgVars[p]; ok {
		return v.val
	}
	return p
}

// -----------------------------------------------------------------------------
