package development

import (
	"errors"

	"github.com/ccoveille/go-safecast"

	log "github.com/authzed/spicedb/internal/logging"
	devinterface "github.com/authzed/spicedb/pkg/proto/developer/v1"
	"github.com/authzed/spicedb/pkg/schemadsl/compiler"
	"github.com/authzed/spicedb/pkg/schemadsl/input"
)

// CompileSchema compiles a schema into its caveat and namespace definition(s), returning a developer
// error if the schema could not be compiled. The non-developer error is returned only if an
// internal errors occurred.
func CompileSchema(schema string) (*compiler.CompiledSchema, *devinterface.DeveloperError, error) {
	compiled, err := compiler.Compile(compiler.InputSchema{
		Source:       input.Source("schema"),
		SchemaString: schema,
	}, compiler.AllowUnprefixedObjectType())

	var contextError compiler.WithContextError
	if errors.As(err, &contextError) {
		line, col, lerr := contextError.SourceRange.Start().LineAndColumn()
		if lerr != nil {
			return nil, nil, lerr
		}

		// NOTE: zeroes are fine here on failure.
		uintLine, err := safecast.ToUint32(line)
		if err != nil {
			log.Err(err).Msg("could not cast lineNumber to uint32")
		}
		uintColumn, err := safecast.ToUint32(col)
		if err != nil {
			log.Err(err).Msg("could not cast columnPosition to uint32")
		}
		return nil, &devinterface.DeveloperError{
			Message: contextError.BaseMessage,
			Kind:    devinterface.DeveloperError_SCHEMA_ISSUE,
			Source:  devinterface.DeveloperError_SCHEMA,
			Line:    uintLine + 1,   // 0-indexed in parser.
			Column:  uintColumn + 1, // 0-indexed in parser.
			Context: contextError.ErrorSourceCode,
		}, nil
	}

	if err != nil {
		return nil, nil, err
	}

	return compiled, nil, nil
}
