// Package fileio provides SQL functions to read, write and list files.
//
// https://sqlite.org/src/doc/tip/ext/misc/fileio.c
package fileio

import (
	"errors"
	"fmt"
	"io/fs"
	"os"

	"github.com/ncruces/go-sqlite3"
)

// Register registers SQL functions readfile, writefile, lsmode,
// and the table-valued function fsdir.
func Register(db *sqlite3.Conn) error {
	return RegisterFS(db, nil)
}

// RegisterFS registers SQL functions readfile, lsmode,
// and the table-valued function fsdir;
// fsys will be used to read files and list directories.
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error {
	var err error
	if fsys == nil {
		err = db.CreateFunction("writefile", -1, sqlite3.DIRECTONLY, writefile)
	}
	return errors.Join(err,
		db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fsys)),
		db.CreateFunction("lsmode", 1, sqlite3.DETERMINISTIC, lsmode),
		sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
			err := db.DeclareVTab(`CREATE TABLE x(name TEXT,mode INT,mtime TIMESTAMP,data BLOB,path HIDDEN,dir HIDDEN)`)
			if err == nil {
				err = db.VTabConfig(sqlite3.VTAB_DIRECTONLY)
			}
			return fsdir{fsys}, err
		}))
}

func lsmode(ctx sqlite3.Context, arg ...sqlite3.Value) {
	ctx.ResultText(fs.FileMode(arg[0].Int()).String())
}

func readfile(fsys fs.FS) sqlite3.ScalarFunction {
	return func(ctx sqlite3.Context, arg ...sqlite3.Value) {
		var err error
		var data []byte

		if fsys != nil {
			data, err = fs.ReadFile(fsys, arg[0].Text())
		} else {
			data, err = os.ReadFile(arg[0].Text())
		}

		switch {
		case err == nil:
			ctx.ResultBlob(data)
		case !errors.Is(err, fs.ErrNotExist):
			ctx.ResultError(fmt.Errorf("readfile: %w", err)) // notest
		}
	}
}
