package internal

import (
	"errors"
	"net/url"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/storage"
)

var errNoAppID = errors.New("storage API requires a unique ID, use app.NewWithID()")

// Docs is an internal implementation of the document features of the Storage interface.
// It is based on top of the current `file` repository and is rooted at RootDocURI.
type Docs struct {
	RootDocURI fyne.URI
}

// Create will create a new document ready for writing, you must write something and close the returned writer
// for the create process to complete.
// If the document for this app with that name already exists a storage.ErrAlreadyExists error will be returned.
func (d *Docs) Create(name string) (fyne.URIWriteCloser, error) {
	if d.RootDocURI == nil {
		return nil, errNoAppID
	}

	err := d.ensureRootExists()
	if err != nil {
		return nil, err
	}

	u, err := d.childURI(name)
	if err != nil {
		return nil, err
	}

	exists, err := storage.Exists(u)
	if err != nil {
		return nil, err
	}
	if exists {
		return nil, storage.ErrAlreadyExists
	}

	return storage.Writer(u)
}

// List returns all documents that have been saved by the current application.
// Remember to use `app.NewWithID` so that your storage is unique.
func (d *Docs) List() []string {
	if d.RootDocURI == nil {
		return nil
	}

	uris, err := storage.List(d.RootDocURI)
	if err != nil {
		return nil
	}

	ret := make([]string, len(uris))
	for i, u := range uris {
		ret[i] = u.Name()
		if d.RootDocURI.Scheme() != "file" {
			ret[i], _ = url.PathUnescape(u.Name())
		}
	}

	return ret
}

// Open will grant access to the contents of the named file. If an error occurs it is returned instead.
func (d *Docs) Open(name string) (fyne.URIReadCloser, error) {
	if d.RootDocURI == nil {
		return nil, errNoAppID
	}

	u, err := d.childURI(name)
	if err != nil {
		return nil, err
	}

	return storage.Reader(u)
}

// Remove will delete the document with the specified name, if it exists
func (d *Docs) Remove(name string) error {
	if d.RootDocURI == nil {
		return errNoAppID
	}

	u, err := d.childURI(name)
	if err != nil {
		return err
	}

	return storage.Delete(u)
}

// Save will open a document ready for writing, you close the returned writer for the save to complete.
// If the document for this app with that name does not exist a storage.ErrNotExists error will be returned.
func (d *Docs) Save(name string) (fyne.URIWriteCloser, error) {
	if d.RootDocURI == nil {
		return nil, errNoAppID
	}

	u, err := d.childURI(name)
	if err != nil {
		return nil, err
	}

	exists, err := storage.Exists(u)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, storage.ErrNotExists
	}

	return storage.Writer(u)
}

func (d *Docs) ensureRootExists() error {
	exists, err := storage.Exists(d.RootDocURI)
	if err != nil {
		return err
	}
	if exists {
		return nil
	}

	return storage.CreateListable(d.RootDocURI)
}

func (d *Docs) childURI(name string) (fyne.URI, error) {
	encoded := name
	if d.RootDocURI.Scheme() != "file" {
		encoded = url.PathEscape(name)
	}

	return storage.Child(d.RootDocURI, encoded)
}
