package orm

import (
	

	
	
)

type join struct {
	Parent    *join
	BaseModel TableModel
	JoinModel TableModel
	Rel       *Relation

	ApplyQuery func(*Query) (*Query, error)
	Columns    []string
	on         []*condAppender
}

func ( *join) ( *condAppender) {
	.on = append(.on, )
}

func ( *join) ( QueryFormatter,  *Query) error {
	switch .Rel.Type {
	case HasManyRelation:
		return .selectMany(, )
	case Many2ManyRelation:
		return .selectM2M(, )
	}
	panic("not reached")
}

func ( *join) ( QueryFormatter,  *Query) error {
	,  := .manyQuery()
	if  != nil {
		return 
	}
	if  == nil {
		return nil
	}
	return .Select()
}

func ( *join) ( *Query) (*Query, error) {
	 := newManyModel()
	if  == nil {
		return nil, nil
	}

	 = .Model()
	if .ApplyQuery != nil {
		var  error
		,  = .ApplyQuery()
		if  != nil {
			return nil, 
		}
	}

	if len(.columns) == 0 {
		.columns = append(.columns, &hasManyColumnsAppender{})
	}

	 := .BaseModel.Table()
	var  []byte
	if len(.Rel.JoinFKs) > 1 {
		 = append(, '(')
	}
	 = appendColumns(, .JoinModel.Table().Alias, .Rel.JoinFKs)
	if len(.Rel.JoinFKs) > 1 {
		 = append(, ')')
	}
	 = append(, " IN ("...)
	 = appendChildValues(
		, .JoinModel.Root(), .JoinModel.ParentIndex(), .Rel.BaseFKs)
	 = append(, ")"...)
	 = .Where(internal.BytesToString())

	if .Rel.Polymorphic != nil {
		 = .Where(`? IN (?, ?)`,
			.Rel.Polymorphic.Column,
			.ModelName, .TypeName)
	}

	return , nil
}

func ( *join) ( QueryFormatter,  *Query) error {
	,  := .m2mQuery(, )
	if  != nil {
		return 
	}
	if  == nil {
		return nil
	}
	return .Select()
}

func ( *join) ( QueryFormatter,  *Query) (*Query, error) {
	 := newM2MModel()
	if  == nil {
		return nil, nil
	}

	 = .Model()
	if .ApplyQuery != nil {
		var  error
		,  = .ApplyQuery()
		if  != nil {
			return nil, 
		}
	}

	if len(.columns) == 0 {
		.columns = append(.columns, &hasManyColumnsAppender{})
	}

	 := .JoinModel.ParentIndex()
	 := .BaseModel.Table()

	//nolint
	var  []byte
	 = append(, "JOIN "...)
	 = .FormatQuery(, string(.Rel.M2MTableName))
	 = append(, " AS "...)
	 = append(, .Rel.M2MTableAlias...)
	 = append(, " ON ("...)
	for ,  := range .Rel.M2MBaseFKs {
		if  > 0 {
			 = append(, ", "...)
		}
		 = append(, .Rel.M2MTableAlias...)
		 = append(, '.')
		 = types.AppendIdent(, , 1)
	}
	 = append(, ") IN ("...)
	 = appendChildValues(, .BaseModel.Root(), , .PKs)
	 = append(, ")"...)
	 = .Join(internal.BytesToString())

	 := .JoinModel.Table()
	for ,  := range .Rel.M2MJoinFKs {
		 := .PKs[]
		 = .Where("?.? = ?.?",
			.Alias, .Column,
			.Rel.M2MTableAlias, types.Ident())
	}

	return , nil
}

func ( *join) () bool {
	if .Parent != nil {
		switch .Parent.Rel.Type {
		case HasOneRelation, BelongsToRelation:
			return true
		}
	}
	return false
}

func ( *join) ( []byte) []byte {
	 = append(, '"')
	 = appendAlias(, )
	 = append(, '"')
	return 
}

func ( *join) ( []byte,  string) []byte {
	 = append(, '"')
	 = appendAlias(, )
	 = append(, "__"...)
	 = append(, ...)
	 = append(, '"')
	return 
}

func ( *join) ( []byte) []byte {
	if .hasParent() {
		 = append(, '"')
		 = appendAlias(, .Parent)
		 = append(, '"')
		return 
	}
	return append(, .BaseModel.Table().Alias...)
}

func ( *join) ( []byte,  queryFlag) []byte {
	 = append(, '.')
	 = append(, .JoinModel.Table().SoftDeleteField.Column...)
	if hasFlag(, deletedFlag) {
		 = append(, " IS NOT NULL"...)
	} else {
		 = append(, " IS NULL"...)
	}
	return 
}

func ( []byte,  *join) []byte {
	if .hasParent() {
		 = (, .Parent)
		 = append(, "__"...)
	}
	 = append(, .Rel.Field.SQLName...)
	return 
}

func ( *join) ( []byte) []byte {
	if .Columns == nil {
		for ,  := range .JoinModel.Table().Fields {
			if  > 0 {
				 = append(, ", "...)
			}
			 = .appendAlias()
			 = append(, '.')
			 = append(, .Column...)
			 = append(, " AS "...)
			 = .appendAliasColumn(, .SQLName)
		}
		return 
	}

	for ,  := range .Columns {
		if  > 0 {
			 = append(, ", "...)
		}
		 = .appendAlias()
		 = append(, '.')
		 = types.AppendIdent(, , 1)
		 = append(, " AS "...)
		 = .appendAliasColumn(, )
	}

	return 
}

func ( *join) ( QueryFormatter,  []byte,  *Query) ( []byte,  error) {
	 := .JoinModel.Table().SoftDeleteField != nil && !.hasFlag(allWithDeletedFlag)

	 = append(, "LEFT JOIN "...)
	 = .FormatQuery(, string(.JoinModel.Table().SQLNameForSelects))
	 = append(, " AS "...)
	 = .appendAlias()

	 = append(, " ON "...)

	if  {
		 = append(, '(')
	}

	if len(.Rel.BaseFKs) > 1 {
		 = append(, '(')
	}
	for ,  := range .Rel.BaseFKs {
		if  > 0 {
			 = append(, " AND "...)
		}
		 = .appendAlias()
		 = append(, '.')
		 = append(, .Rel.JoinFKs[].Column...)
		 = append(, " = "...)
		 = .appendBaseAlias()
		 = append(, '.')
		 = append(, .Column...)
	}
	if len(.Rel.BaseFKs) > 1 {
		 = append(, ')')
	}

	for ,  := range .on {
		 = .AppendSep()
		,  = .AppendQuery(, )
		if  != nil {
			return nil, 
		}
	}

	if  {
		 = append(, ')')
	}

	if  {
		 = append(, " AND "...)
		 = .appendAlias()
		 = .appendSoftDelete(, .flags)
	}

	return , nil
}

type hasManyColumnsAppender struct {
	*join
}

var _ QueryAppender = (*hasManyColumnsAppender)(nil)

func ( *hasManyColumnsAppender) ( QueryFormatter,  []byte) ([]byte, error) {
	if .Rel.M2MTableAlias != "" {
		 = append(, .Rel.M2MTableAlias...)
		 = append(, ".*, "...)
	}

	 := .JoinModel.Table()

	if .Columns != nil {
		for ,  := range .Columns {
			if  > 0 {
				 = append(, ", "...)
			}
			 = append(, .Alias...)
			 = append(, '.')
			 = types.AppendIdent(, , 1)
		}
		return , nil
	}

	 = appendColumns(, .Alias, .Fields)
	return , nil
}

func ( []byte,  reflect.Value,  []int,  []*Field) []byte {
	 := make(map[string]struct{})
	walk(, , func( reflect.Value) {
		 := len()

		if len() > 1 {
			 = append(, '(')
		}
		for ,  := range  {
			if  > 0 {
				 = append(, ", "...)
			}
			 = .AppendValue(, , 1)
		}
		if len() > 1 {
			 = append(, ')')
		}
		 = append(, ", "...)

		if ,  := [string([:])];  {
			 = [:]
		} else {
			[string([:])] = struct{}{}
		}
	})
	if len() > 0 {
		 = [:len()-2] // trim ", "
	}
	return 
}