// Copyright 2025 The Sqlite Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sqlite // import "modernc.org/sqlite"

import (
	"database/sql/driver"
	"fmt"
	"io"
	"math"
	"reflect"
	"strings"
	"time"

	sqlite3 "modernc.org/sqlite/lib"
)

type rows struct {
	allocs  []uintptr
	c       *conn
	columns []string
	// decltypes holds the uppercased declared types for every result column,
	// captured once at newRows time. The declared type of a column is fixed
	// for the lifetime of a prepared statement, so caching the result of
	// strings.ToUpper(sqlite3_column_decltype(...)) here removes a per-row
	// libc.GoString + strings.ToUpper from the Next() hot path for callers
	// that hit the time-conversion branches (_texttotime, _time_format).
	decltypes []string
	// parseFmtIdx caches, per column, the index into parseTimeFormats that
	// matched the first successful (*conn).parseTime call on that column.
	// Subsequent rows reuse the saved index as the first attempt instead of
	// re-walking the format list from the top. Slot value -1 means no match
	// has been recorded yet (either parseTime has not run on this column, or
	// the parseTimeString / m= branch matched, which is not in
	// parseTimeFormats). The cache is sticky: once a successful index is
	// stored it is not overwritten if a later row happens to match a
	// different format. A steady-format column therefore wins on every row
	// after the first; a mixed-format column falls through as before, paying
	// at most one extra format probe on rows whose matching format precedes
	// the cached one.
	parseFmtIdx []int8
	pstmt       uintptr

	doStep    bool
	empty     bool
	reuseStmt bool // If true, Close() resets instead of finalizing
}

func newRows(c *conn, pstmt uintptr, allocs *[]uintptr, empty bool) (r *rows, err error) {
	var a []uintptr
	if allocs != nil {
		a = *allocs
		*allocs = nil
	}
	r = &rows{c: c, pstmt: pstmt, allocs: a, empty: empty}

	defer func() {
		if err != nil {
			r.Close()
			r = nil
		}
	}()

	n, err := c.columnCount(pstmt)
	if err != nil {
		return nil, err
	}

	r.columns = make([]string, n)
	r.decltypes = make([]string, n)
	r.parseFmtIdx = make([]int8, n)
	for i := range r.columns {
		if r.columns[i], err = r.c.columnName(pstmt, i); err != nil {
			return nil, err
		}
		r.decltypes[i] = strings.ToUpper(r.c.columnDeclType(pstmt, i))
		r.parseFmtIdx[i] = -1
	}

	return r, nil
}

// Close closes the rows iterator.
func (r *rows) Close() (err error) {
	r.c.freeAllocs(r.allocs)
	r.allocs = nil

	if r.reuseStmt {
		// Reset the statement for reuse instead of finalizing it
		if e := r.c.reset(r.pstmt); e != nil {
			return e
		}
		return r.c.clearBindings(r.pstmt)
	}

	return r.c.finalize(r.pstmt)
}

// Columns returns the names of the columns. The number of columns of the
// result is inferred from the length of the slice. If a particular column name
// isn't known, an empty string should be returned for that entry.
func (r *rows) Columns() (c []string) {
	return r.columns
}

// Next is called to populate the next row of data into the provided slice. The
// provided slice will be the same size as the Columns() are wide.
//
// Next should return io.EOF when there are no more rows.
func (r *rows) Next(dest []driver.Value) (err error) {
	if r.empty {
		return io.EOF
	}

	rc := sqlite3.SQLITE_ROW
	if r.doStep {
		if rc, err = r.c.step(r.pstmt); err != nil {
			return err
		}
	}

	r.doStep = true
	switch rc {
	case sqlite3.SQLITE_ROW:
		if g, e := len(dest), len(r.columns); g != e {
			return fmt.Errorf("sqlite: Next: have %v destination values, expected %v", g, e)
		}

		for i := range dest {
			ct, err := r.c.columnType(r.pstmt, i)
			if err != nil {
				return err
			}

			switch ct {
			case sqlite3.SQLITE_INTEGER:
				v, err := r.c.columnInt64(r.pstmt, i)
				if err != nil {
					return err
				}

				if !r.c.intToTime {
					dest[i] = v
				} else {
					// Inspired by mattn/go-sqlite3:
					// https://github.com/mattn/go-sqlite3/blob/f76bae4b0044cbba8fb2c72b8e4559e8fbcffd86/sqlite3.go#L2254-L2262
					// but we put make this compatibility optional behind a DSN
					// query parameter, because this changes API behavior, so an
					// opt-in is needed.

					switch r.ColumnTypeDatabaseTypeName(i) {
					case "DATE", "DATETIME", "TIMESTAMP":
						// Check for explicit opt-in first.  This fixes the bug for micro/nano users
						// without breaking the legacy heuristic for existing users.
						switch r.c.integerTimeFormat {
						case "unix_micro":
							dest[i] = r.c.applyTimezone(time.UnixMicro(v).UTC())
							continue
						case "unix_nano":
							dest[i] = r.c.applyTimezone(time.Unix(0, v).UTC())
							continue
						}

						// Legacy Heuristic (mattn/go-sqlite3 compatibility).  NOTE: This heuristic
						// fails for Millisecond timestamps representing dates before Sept 9, 2001
						// (value < 1e12).

						// Is it a seconds timestamp or a milliseconds
						// timestamp?
						if v > 1e12 || v < -1e12 {
							// Milliseconds
							dest[i] = r.c.applyTimezone(time.UnixMilli(v).UTC())
						} else {
							// Seconds
							dest[i] = r.c.applyTimezone(time.Unix(v, 0).UTC())
						}
					default:
						dest[i] = v
					}
				}
			case sqlite3.SQLITE_FLOAT:
				v, err := r.c.columnDouble(r.pstmt, i)
				if err != nil {
					return err
				}

				dest[i] = v
			case sqlite3.SQLITE_TEXT:
				v, err := r.c.columnText(r.pstmt, i)
				if err != nil {
					return err
				}

				switch r.ColumnTypeDatabaseTypeName(i) {
				case "DATE", "DATETIME", "TIMESTAMP":
					val, ok, idx := r.c.parseTime(v, int(r.parseFmtIdx[i]))
					if ok && r.parseFmtIdx[i] < 0 && idx >= 0 {
						r.parseFmtIdx[i] = int8(idx)
					}
					dest[i] = val
				default:
					dest[i] = v
				}
			case sqlite3.SQLITE_BLOB:
				v, err := r.c.columnBlob(r.pstmt, i)
				if err != nil {
					return err
				}

				dest[i] = v
			case sqlite3.SQLITE_NULL:
				dest[i] = nil
			default:
				return fmt.Errorf("internal error: rc %d", rc)
			}
		}
		return nil
	case sqlite3.SQLITE_DONE:
		return io.EOF
	default:
		return r.c.errstr(int32(rc))
	}
}

// RowsColumnTypeDatabaseTypeName may be implemented by Rows. It should return
// the database system type name without the length. Type names should be
// uppercase. Examples of returned types: "VARCHAR", "NVARCHAR", "VARCHAR2",
// "CHAR", "TEXT", "DECIMAL", "SMALLINT", "INT", "BIGINT", "BOOL", "[]BIGINT",
// "JSONB", "XML", "TIMESTAMP".
func (r *rows) ColumnTypeDatabaseTypeName(index int) string {
	return r.decltypes[index]
}

// RowsColumnTypeLength may be implemented by Rows. It should return the length
// of the column type if the column is a variable length type. If the column is
// not a variable length type ok should return false. If length is not limited
// other than system limits, it should return math.MaxInt64. The following are
// examples of returned values for various types:
//
//	TEXT          (math.MaxInt64, true)
//	varchar(10)   (10, true)
//	nvarchar(10)  (10, true)
//	decimal       (0, false)
//	int           (0, false)
//	bytea(30)     (30, true)
func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) {
	t, err := r.c.columnType(r.pstmt, index)
	if err != nil {
		return 0, false
	}

	switch t {
	case sqlite3.SQLITE_INTEGER:
		return 0, false
	case sqlite3.SQLITE_FLOAT:
		return 0, false
	case sqlite3.SQLITE_TEXT:
		return math.MaxInt64, true
	case sqlite3.SQLITE_BLOB:
		return math.MaxInt64, true
	case sqlite3.SQLITE_NULL:
		return 0, false
	default:
		return 0, false
	}
}

// RowsColumnTypeNullable may be implemented by Rows. The nullable value should
// be true if it is known the column may be null, or false if the column is
// known to be not nullable. If the column nullability is unknown, ok should be
// false.
func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) {
	return true, true
}

// RowsColumnTypePrecisionScale may be implemented by Rows. It should return
// the precision and scale for decimal types. If not applicable, ok should be
// false. The following are examples of returned values for various types:
//
//	decimal(38, 4)    (38, 4, true)
//	int               (0, 0, false)
//	decimal           (math.MaxInt64, math.MaxInt64, true)
func (r *rows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
	return 0, 0, false
}

// RowsColumnTypeScanType may be implemented by Rows. It should return the
// value type that can be used to scan types into. For example, the database
// column type "bigint" this should return "reflect.TypeOf(int64(0))".
func (r *rows) ColumnTypeScanType(index int) reflect.Type {
	t, err := r.c.columnType(r.pstmt, index)
	if err != nil {
		return reflect.TypeOf("")
	}

	switch t {
	case sqlite3.SQLITE_INTEGER:
		switch r.decltypes[index] {
		case "BOOLEAN":
			return reflect.TypeOf(false)
		case "DATE", "DATETIME", "TIME", "TIMESTAMP":
			return reflect.TypeOf(time.Time{})
		default:
			return reflect.TypeOf(int64(0))
		}
	case sqlite3.SQLITE_FLOAT:
		return reflect.TypeOf(float64(0))
	case sqlite3.SQLITE_TEXT:
		if r.c.textToTime {
			switch r.decltypes[index] {
			case "DATE", "DATETIME", "TIME", "TIMESTAMP":
				return reflect.TypeOf(time.Time{})
			}
		}
		return reflect.TypeOf("")
	case sqlite3.SQLITE_BLOB:
		return reflect.TypeOf([]byte(nil))
	case sqlite3.SQLITE_NULL:
		return reflect.TypeOf(nil)
	default:
		return reflect.TypeOf("")
	}
}

// C documentation
//
//	int sqlite3_reset(sqlite3_stmt *pStmt);
func (c *conn) reset(pstmt uintptr) error {
	if rc := sqlite3.Xsqlite3_reset(c.tls, pstmt); rc != sqlite3.SQLITE_OK {
		return c.errstr(rc)
	}
	return nil
}
