// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Code generated by the Google Gen AI SDK generator DO NOT EDIT.

package genai

import (
	"context"
	"fmt"
	"io"
	"iter"
	"mime"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

func createFileParametersToMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromFile := getValueByPath(fromObject, []string{"file"})
	if fromFile != nil {
		setValueByPath(toObject, []string{"file"}, fromFile)
	}

	return toObject, nil
}

func createFileResponseFromMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromSdkHttpResponse := getValueByPath(fromObject, []string{"sdkHttpResponse"})
	if fromSdkHttpResponse != nil {
		setValueByPath(toObject, []string{"sdkHttpResponse"}, fromSdkHttpResponse)
	}

	return toObject, nil
}

func deleteFileParametersToMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromName := getValueByPath(fromObject, []string{"name"})
	if fromName != nil {
		fromName, err = tFileName(fromName)
		if err != nil {
			return nil, err
		}

		setValueByPath(toObject, []string{"_url", "file"}, fromName)
	}

	return toObject, nil
}

func deleteFileResponseFromMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromSdkHttpResponse := getValueByPath(fromObject, []string{"sdkHttpResponse"})
	if fromSdkHttpResponse != nil {
		setValueByPath(toObject, []string{"sdkHttpResponse"}, fromSdkHttpResponse)
	}

	return toObject, nil
}

func getFileParametersToMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromName := getValueByPath(fromObject, []string{"name"})
	if fromName != nil {
		fromName, err = tFileName(fromName)
		if err != nil {
			return nil, err
		}

		setValueByPath(toObject, []string{"_url", "file"}, fromName)
	}

	return toObject, nil
}

func listFilesConfigToMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromPageSize := getValueByPath(fromObject, []string{"pageSize"})
	if fromPageSize != nil {
		setValueByPath(parentObject, []string{"_query", "pageSize"}, fromPageSize)
	}

	fromPageToken := getValueByPath(fromObject, []string{"pageToken"})
	if fromPageToken != nil {
		setValueByPath(parentObject, []string{"_query", "pageToken"}, fromPageToken)
	}

	return toObject, nil
}

func listFilesParametersToMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromConfig := getValueByPath(fromObject, []string{"config"})
	if fromConfig != nil {
		_, err = listFilesConfigToMldev(fromConfig.(map[string]any), toObject, rootObject)
		if err != nil {
			return nil, err
		}
	}

	return toObject, nil
}

func listFilesResponseFromMldev(fromObject map[string]any, parentObject map[string]any, rootObject map[string]any) (toObject map[string]any, err error) {
	toObject = make(map[string]any)

	fromSdkHttpResponse := getValueByPath(fromObject, []string{"sdkHttpResponse"})
	if fromSdkHttpResponse != nil {
		setValueByPath(toObject, []string{"sdkHttpResponse"}, fromSdkHttpResponse)
	}

	fromNextPageToken := getValueByPath(fromObject, []string{"nextPageToken"})
	if fromNextPageToken != nil {
		setValueByPath(toObject, []string{"nextPageToken"}, fromNextPageToken)
	}

	fromFiles := getValueByPath(fromObject, []string{"files"})
	if fromFiles != nil {
		setValueByPath(toObject, []string{"files"}, fromFiles)
	}

	return toObject, nil
}

type Files struct {
	apiClient *apiClient
}

func (m Files) list(ctx context.Context, config *ListFilesConfig) (*ListFilesResponse, error) {
	parameterMap := make(map[string]any)

	kwargs := map[string]any{"config": config}
	deepMarshal(kwargs, &parameterMap)

	var httpOptions *HTTPOptions
	if config == nil || config.HTTPOptions == nil {
		httpOptions = &HTTPOptions{}
	} else {
		httpOptions = config.HTTPOptions
	}
	if httpOptions.Headers == nil {
		httpOptions.Headers = http.Header{}
	}
	var response = new(ListFilesResponse)
	var responseMap map[string]any
	var fromConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	var toConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	if m.apiClient.clientConfig.Backend == BackendVertexAI {

		return nil, fmt.Errorf("method List is only supported in the Gemini Developer client. You can choose to use Gemini Developer client by setting ClientConfig.Backend to BackendGeminiAPI.")

	} else {
		toConverter = listFilesParametersToMldev
		fromConverter = listFilesResponseFromMldev
	}

	body, err := toConverter(parameterMap, nil, parameterMap)
	if err != nil {
		return nil, err
	}
	var path string
	var urlParams map[string]any
	if _, ok := body["_url"]; ok {
		urlParams = body["_url"].(map[string]any)
		delete(body, "_url")
	}
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		path, err = formatMap("None", urlParams)
	} else {
		path, err = formatMap("files", urlParams)
	}
	if err != nil {
		return nil, fmt.Errorf("invalid url params: %#v.\n%w", urlParams, err)
	}
	if _, ok := body["_query"]; ok {
		query, err := createURLQuery(body["_query"].(map[string]any))
		if err != nil {
			return nil, err
		}
		path += "?" + query
		delete(body, "_query")
	}
	responseMap, err = sendRequest(ctx, m.apiClient, path, http.MethodGet, body, httpOptions)
	if err != nil {
		return nil, err
	}
	if fromConverter != nil {
		responseMap, err = fromConverter(responseMap, nil, parameterMap)
	}
	if err != nil {
		return nil, err
	}
	err = mapToStruct(responseMap, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

func (m Files) create(ctx context.Context, file *File, config *CreateFileConfig) (*CreateFileResponse, error) {
	parameterMap := make(map[string]any)

	kwargs := map[string]any{"file": file, "config": config}
	deepMarshal(kwargs, &parameterMap)

	var httpOptions *HTTPOptions
	if config == nil || config.HTTPOptions == nil {
		httpOptions = &HTTPOptions{}
	} else {
		httpOptions = config.HTTPOptions
	}
	if httpOptions.Headers == nil {
		httpOptions.Headers = http.Header{}
	}
	var response = new(CreateFileResponse)
	var responseMap map[string]any
	var fromConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	var toConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	if m.apiClient.clientConfig.Backend == BackendVertexAI {

		return nil, fmt.Errorf("method Create is only supported in the Gemini Developer client. You can choose to use Gemini Developer client by setting ClientConfig.Backend to BackendGeminiAPI.")

	} else {
		toConverter = createFileParametersToMldev
		fromConverter = createFileResponseFromMldev
	}

	body, err := toConverter(parameterMap, nil, parameterMap)
	if err != nil {
		return nil, err
	}
	var path string
	var urlParams map[string]any
	if _, ok := body["_url"]; ok {
		urlParams = body["_url"].(map[string]any)
		delete(body, "_url")
	}
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		path, err = formatMap("None", urlParams)
	} else {
		path, err = formatMap("upload/v1beta/files", urlParams)
	}
	if err != nil {
		return nil, fmt.Errorf("invalid url params: %#v.\n%w", urlParams, err)
	}
	if _, ok := body["_query"]; ok {
		query, err := createURLQuery(body["_query"].(map[string]any))
		if err != nil {
			return nil, err
		}
		path += "?" + query
		delete(body, "_query")
	}
	responseMap, err = sendRequest(ctx, m.apiClient, path, http.MethodPost, body, httpOptions)
	if err != nil {
		return nil, err
	}
	if fromConverter != nil {
		responseMap, err = fromConverter(responseMap, nil, parameterMap)
	}
	if err != nil {
		return nil, err
	}
	err = mapToStruct(responseMap, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

func (m Files) Get(ctx context.Context, name string, config *GetFileConfig) (*File, error) {
	parameterMap := make(map[string]any)

	kwargs := map[string]any{"name": name, "config": config}
	deepMarshal(kwargs, &parameterMap)

	var httpOptions *HTTPOptions
	if config == nil || config.HTTPOptions == nil {
		httpOptions = &HTTPOptions{}
	} else {
		httpOptions = config.HTTPOptions
	}
	if httpOptions.Headers == nil {
		httpOptions.Headers = http.Header{}
	}
	var response = new(File)
	var responseMap map[string]any
	var toConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	if m.apiClient.clientConfig.Backend == BackendVertexAI {

		return nil, fmt.Errorf("method Get is only supported in the Gemini Developer client. You can choose to use Gemini Developer client by setting ClientConfig.Backend to BackendGeminiAPI.")

	} else {
		toConverter = getFileParametersToMldev

	}

	body, err := toConverter(parameterMap, nil, parameterMap)
	if err != nil {
		return nil, err
	}
	var path string
	var urlParams map[string]any
	if _, ok := body["_url"]; ok {
		urlParams = body["_url"].(map[string]any)
		delete(body, "_url")
	}
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		path, err = formatMap("None", urlParams)
	} else {
		path, err = formatMap("files/{file}", urlParams)
	}
	if err != nil {
		return nil, fmt.Errorf("invalid url params: %#v.\n%w", urlParams, err)
	}
	if _, ok := body["_query"]; ok {
		query, err := createURLQuery(body["_query"].(map[string]any))
		if err != nil {
			return nil, err
		}
		path += "?" + query
		delete(body, "_query")
	}
	responseMap, err = sendRequest(ctx, m.apiClient, path, http.MethodGet, body, httpOptions)
	if err != nil {
		return nil, err
	}
	err = mapToStruct(responseMap, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

func (m Files) Delete(ctx context.Context, name string, config *DeleteFileConfig) (*DeleteFileResponse, error) {
	parameterMap := make(map[string]any)

	kwargs := map[string]any{"name": name, "config": config}
	deepMarshal(kwargs, &parameterMap)

	var httpOptions *HTTPOptions
	if config == nil || config.HTTPOptions == nil {
		httpOptions = &HTTPOptions{}
	} else {
		httpOptions = config.HTTPOptions
	}
	if httpOptions.Headers == nil {
		httpOptions.Headers = http.Header{}
	}
	var response = new(DeleteFileResponse)
	var responseMap map[string]any
	var fromConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	var toConverter func(map[string]any, map[string]any, map[string]any) (map[string]any, error)
	if m.apiClient.clientConfig.Backend == BackendVertexAI {

		return nil, fmt.Errorf("method Delete is only supported in the Gemini Developer client. You can choose to use Gemini Developer client by setting ClientConfig.Backend to BackendGeminiAPI.")

	} else {
		toConverter = deleteFileParametersToMldev
		fromConverter = deleteFileResponseFromMldev
	}

	body, err := toConverter(parameterMap, nil, parameterMap)
	if err != nil {
		return nil, err
	}
	var path string
	var urlParams map[string]any
	if _, ok := body["_url"]; ok {
		urlParams = body["_url"].(map[string]any)
		delete(body, "_url")
	}
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		path, err = formatMap("None", urlParams)
	} else {
		path, err = formatMap("files/{file}", urlParams)
	}
	if err != nil {
		return nil, fmt.Errorf("invalid url params: %#v.\n%w", urlParams, err)
	}
	if _, ok := body["_query"]; ok {
		query, err := createURLQuery(body["_query"].(map[string]any))
		if err != nil {
			return nil, err
		}
		path += "?" + query
		delete(body, "_query")
	}
	responseMap, err = sendRequest(ctx, m.apiClient, path, http.MethodDelete, body, httpOptions)
	if err != nil {
		return nil, err
	}
	if fromConverter != nil {
		responseMap, err = fromConverter(responseMap, nil, parameterMap)
	}
	if err != nil {
		return nil, err
	}
	err = mapToStruct(responseMap, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

// List retrieves a paginated list of files resources.
func (m Files) List(ctx context.Context, config *ListFilesConfig) (Page[File], error) {
	listFunc := func(ctx context.Context, config map[string]any) ([]*File, string, *HTTPResponse, error) {
		var c ListFilesConfig
		if err := mapToStruct(config, &c); err != nil {
			return nil, "", nil, err
		}
		resp, err := m.list(ctx, &c)
		if err != nil {
			return nil, "", nil, err
		}
		return resp.Files, resp.NextPageToken, resp.SDKHTTPResponse, nil
	}
	c := make(map[string]any)
	deepMarshal(config, &c)
	return newPage(ctx, "files", c, listFunc)
}

// All retrieves all files resources.
//
// This method handles pagination internally, making multiple API calls as needed
// to fetch all entries. It returns an iterator that yields each "files"
// entry one by one. You do not need to manage pagination
// tokens or make multiple calls to retrieve all data.
func (m Files) All(ctx context.Context) iter.Seq2[*File, error] {
	listFunc := func(ctx context.Context, config map[string]any) ([]*File, string, *HTTPResponse, error) {
		var c ListFilesConfig
		if err := mapToStruct(config, &c); err != nil {
			return nil, "", nil, err
		}
		resp, err := m.list(ctx, &c)
		if err != nil {
			return nil, "", nil, err
		}
		return resp.Files, resp.NextPageToken, resp.SDKHTTPResponse, nil
	}
	p, err := newPage(ctx, "files", map[string]any{}, listFunc)
	if err != nil {
		return yieldErrorAndEndIterator[File](err)
	}
	return p.all(ctx)
}

// Download function downloads a file from the specified URI.
// If the URI refers to a video([Video], [GeneratedVideo]), the video bytes will be populated to the video's VideoBytes field.
func (m Files) Download(ctx context.Context, uri DownloadURI, config *DownloadFileConfig) ([]byte, error) {
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		return nil, fmt.Errorf("method Download is only supported in the Gemini Developer client. You can choose to use Gemini Developer client by setting ClientConfig.Backend to BackendGeminiAPI.")
	}
	if uri.uri() == "" {
		return nil, fmt.Errorf("the resource doesn't support download")
	}
	fileName, err := tFileName(uri.uri())
	if err != nil {
		return nil, err
	}
	path := fmt.Sprintf("files/%s:download?alt=media", fileName)

	var httpOptions *HTTPOptions
	if config == nil {
		httpOptions = mergeHTTPOptions(m.apiClient.clientConfig, nil)
	} else {
		httpOptions = mergeHTTPOptions(m.apiClient.clientConfig, config.HTTPOptions)
		config.HTTPOptions = nil
	}

	data, err := downloadFile(ctx, m.apiClient, path, httpOptions)
	if err != nil {
		return nil, err
	}
	_ = uri.setVideoBytes(data)
	return data, nil
}

// Upload copies the contents of the given io.Reader to file storage associated
// with the service, and returns information about the resulting file.
func (m Files) Upload(ctx context.Context, r io.Reader, config *UploadFileConfig) (*File, error) {
	if m.apiClient.clientConfig.Backend == BackendVertexAI {
		return nil, fmt.Errorf("This method is only supported in the Gemini Developer client.")
	}

	var fileToUpload File
	if config != nil {
		fileToUpload.MIMEType = config.MIMEType
		fileToUpload.Name = config.Name
		fileToUpload.DisplayName = config.DisplayName
	}

	if fileToUpload.Name != "" && !strings.HasPrefix(fileToUpload.Name, "files/") {
		fileToUpload.Name = "files/" + fileToUpload.Name
	}

	httpOptions := HTTPOptions{Headers: http.Header{}}
	if config != nil && config.HTTPOptions != nil {
		deepCopy(*config.HTTPOptions, &httpOptions)
	}
	if httpOptions.Headers == nil {
		httpOptions.Headers = http.Header{}
	}

	httpOptions.APIVersion = ""
	httpOptions.Headers.Add("Content-Type", "application/json")
	httpOptions.Headers.Add("X-Goog-Upload-Protocol", "resumable")
	httpOptions.Headers.Add("X-Goog-Upload-Command", "start")
	httpOptions.Headers.Add("X-Goog-Upload-Header-Content-Type", fileToUpload.MIMEType)

	var createFileConfig CreateFileConfig
	createFileConfig.HTTPOptions = &httpOptions
	createFileConfig.ShouldReturnHTTPResponse = true

	resp, err := m.create(ctx, &fileToUpload, &createFileConfig)
	if err != nil {
		return nil, fmt.Errorf("Failed to create file. Ran into an error: %s", err)
	}
	if resp.SDKHTTPResponse == nil || resp.SDKHTTPResponse.Headers == nil {
		return nil, fmt.Errorf("Failed to create file. Upload URL was not returned from the create file request.")
	}
	uploadURL := resp.SDKHTTPResponse.Headers.Get("X-Goog-Upload-Url")
	if uploadURL == "" {
		return nil, fmt.Errorf("Failed to create file. Upload URL was not returned from the create file request.")
	}
	return m.apiClient.uploadFile(ctx, r, uploadURL, &httpOptions)
}

// UploadFromPath uploads a file from the specified path and returns information
// about the resulting file.
func (m Files) UploadFromPath(ctx context.Context, path string, config *UploadFileConfig) (*File, error) {
	fileInfo, err := os.Stat(path)
	if err != nil || fileInfo.IsDir() {
		return nil, fmt.Errorf("%s is not a valid file path.", path)
	}

	osf, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer osf.Close()

	if config == nil {
		config = &UploadFileConfig{}
	}

	var copiedCfg UploadFileConfig
	deepCopy(*config, &copiedCfg)

	if copiedCfg.MIMEType == "" {
		copiedCfg.MIMEType = mime.TypeByExtension(filepath.Ext(path))
		if copiedCfg.MIMEType == "" {
			return nil, fmt.Errorf("Unknown mime type: Could not determine the mimetype for your file please set the `MIMEType` argument")
		}
	}

	if copiedCfg.HTTPOptions == nil {
		copiedCfg.HTTPOptions = &HTTPOptions{Headers: http.Header{}}
	}
	if copiedCfg.HTTPOptions.Headers == nil {
		copiedCfg.HTTPOptions.Headers = http.Header{}
	}
	copiedCfg.HTTPOptions.Headers.Add("X-Goog-Upload-Header-Content-Length", strconv.FormatInt(fileInfo.Size(), 10))

	fileName := filepath.Base(path)
	copiedCfg.HTTPOptions.Headers.Add("X-Goog-Upload-File-Name", fileName)

	return m.Upload(ctx, osf, &copiedCfg)
}
