package orm

import (
	"database/sql"
	"errors"
	"fmt"
	"reflect"

	"github.com/go-pg/pg/v10/types"
)

var errModelNil = errors.New("pg: Model(nil)")

type useQueryOne interface {
	useQueryOne() bool
}

type HooklessModel interface {
	// Init is responsible to initialize/reset model state.
	// It is called only once no matter how many rows were returned.
	Init() error

	// NextColumnScanner returns a ColumnScanner that is used to scan columns
	// from the current row. It is called once for every row.
	NextColumnScanner() ColumnScanner

	// AddColumnScanner adds the ColumnScanner to the model.
	AddColumnScanner(ColumnScanner) error
}

type Model interface {
	HooklessModel

	AfterSelectHook

	BeforeInsertHook
	AfterInsertHook

	BeforeUpdateHook
	AfterUpdateHook

	BeforeDeleteHook
	AfterDeleteHook
}

func NewModel(values ...interface{}) (Model, error) {
	if len(values) > 1 {
		return Scan(values...), nil
	}

	v0 := values[0]
	switch v0 := v0.(type) {
	case Model:
		return v0, nil
	case HooklessModel:
		return newModelWithHookStubs(v0), nil
	case types.ValueScanner, sql.Scanner:
		return Scan(v0), nil
	}

	v := reflect.ValueOf(v0)
	if !v.IsValid() {
		return nil, errModelNil
	}
	if v.Kind() != reflect.Ptr {
		return nil, fmt.Errorf("pg: Model(non-pointer %T)", v0)
	}
	v = v.Elem()

	switch v.Kind() {
	case reflect.Struct:
		if v.Type() != timeType {
			return newStructTableModelValue(v), nil
		}
	case reflect.Slice:
		typ := v.Type()
		elemType := indirectType(typ.Elem())
		if elemType.Kind() == reflect.Struct && elemType != timeType {
			return newSliceTableModel(v, elemType), nil
		}
		return newSliceModel(v, elemType), nil
	}

	return Scan(v0), nil
}

type modelWithHookStubs struct {
	hookStubs
	HooklessModel
}

func newModelWithHookStubs(m HooklessModel) Model {
	return modelWithHookStubs{
		HooklessModel: m,
	}
}