# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/google/classes/GDrive_v2.ipynb.

# %% auto 0
__all__ = ['walk_folder_and_flatten', 'walk_folder', 'GDrive_MimeType_Enum', 'create_gfile', 'replace_gfile', 'delete_gfile',
           'GDrive_ExportError', 'export_file_as', 'export_as_pptx', 'export_as_zip', 'ExportAs_Enum',
           'GDrive_Metadata', 'GDrive_ServiceRequired', 'GDrive_File', 'Custom_Document_Metadata',
           'Enhanced_GDrive_File', 'GDrive_Folder', 'export_metadata_to_file']

# %% ../../nbs/google/classes/GDrive_v2.ipynb 2
from domolibrary_extensions.google.routes.drive import (
    DEFAULT_GDRIVE_FIELDS,
    DEFAULT_GFOLDER_FIELDS,
)
from googleapiclient.http import MediaFileUpload

# %% ../../nbs/google/classes/GDrive_v2.ipynb 3
from domolibrary_extensions.utils.utils import (
    upsert_folder,
    convert_str_to_date,
    download_zip,
    download_pptx,
)

import domolibrary_extensions.client as client
import domolibrary_extensions.utils.execution as ce
import domolibrary_extensions.google.GAuth as ga
import domolibrary_extensions.google.routes.drive as drive_routes

from googleapiclient.errors import HttpError

from functools import partial
import re
from abc import ABC, abstractmethod

from dataclasses import dataclass, field
from enum import Enum
from typing import List, Any, Optional, Callable, Dict

import datetime as dt

# %% ../../nbs/google/classes/GDrive_v2.ipynb 6
async def walk_folder_and_flatten(
    service: ga.Resource,
    folder_id: str,
    file_ls: List[dict] = None,
    is_recursive: bool = True,
    parent_ls: Optional[List[str]] = None,
    is_just_return_folders: bool = False,
    debug_api: bool = False,
):  # recursive function that retrieves all files in subfolders

    if file_ls is None:
        file_ls = []

    parent_ls = parent_ls.copy() if parent_ls else [folder_id]

    new_files = await drive_routes.get_folder_contents_by_id(
        service,
        folder_id,
        parent_ls=parent_ls,
        is_just_return_folders=is_just_return_folders,
        debug_api=debug_api,
    )
    file_ls.extend(new_files)

    if new_files and is_recursive:
        await ce.gather_with_concurrency(
            *[
                walk_folder_and_flatten(
                    service=service,
                    folder_id=file_obj["id"],
                    file_ls=file_ls,
                    parent_ls=[
                        *parent_ls,
                        *[
                            parent
                            for parent in file_obj["parent_ls"]
                            if parent not in parent_ls
                        ],
                    ],
                    is_just_return_folders=is_just_return_folders,
                    debug_api=debug_api,
                )
                for file_obj in new_files
                if file_obj.get("mimeType") == "application/vnd.google-apps.folder"
            ],
            n=5,
        )

    return file_ls

# %% ../../nbs/google/classes/GDrive_v2.ipynb 8
async def walk_folder(
    service: ga.Resource,
    folder_id: str,
    folder_obj: dict = None,
    parent_ls: List[str] = None,
    is_recursive: bool = True,
):  # recursive function that retrieves all files in subfolders

    parent_ls = parent_ls.copy() if parent_ls else [folder_id]

    folder_obj = folder_obj or (
        await drive_routes.get_file_by_id(
            service=service, file_id=folder_id, parent_ls=parent_ls
        )
    )

    folder_obj["children"] = (
        await drive_routes.get_folder_contents_by_id(
            service, folder_id, is_just_return_folders=False, parent_ls=parent_ls
        )
    ) or []

    if not is_recursive:
        return folder_obj

    for child in folder_obj["children"]:
        if child and child.get("mimeType") == "application/vnd.google-apps.folder":
            await walk_folder(
                service=service,
                folder_obj=child,
                folder_id=child.get("id"),
                parent_ls=parent_ls,
                is_recursive=is_recursive,
            )

    return folder_obj

# %% ../../nbs/google/classes/GDrive_v2.ipynb 10
class GDrive_MimeType_Enum(client.DE_Enum):
    """for translating Google Drive types into 'exports as'"""

    docx = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
    pptx = "application/vnd.openxmlformats-officedocument.presentationml.presentation"

    odt = "application/vnd.oasis.opendocument.text"
    rtf = "application/rtf"
    pdf = "application/pdf"
    txt = "text/plain"

    zip_file = "application/zip"
    epub = "application/epub+zip"

    folder = "application/vnd.google-apps.folder"
    gdoc = "application/vnd.google-apps.document"
    gsheet = "application/vnd.google-apps.spreadsheet"
    gshortcut = "application/vnd.google-apps.shortcut"
    gslides = "application/vnd.google-apps.presentation"

    markdown = "text/markdown"

    # html = 'application/zip'
    # 'tex'  = 'application/zip'
    # 'html.zip'= 'application/zip'

# %% ../../nbs/google/classes/GDrive_v2.ipynb 11
async def create_gfile(
    service: ga.Resource,
    media: MediaFileUpload,
    file_name: str,
    mime_type_enum: GDrive_MimeType_Enum = GDrive_MimeType_Enum.gdoc,
    debug_prn: bool = False,
    parent_folder: str = None,
    debug_api: bool = False,
    return_fields: List[str] = None,
):

    file_metadata = {
        "name": file_name,
        "mimeType": mime_type_enum.value,
    }

    if parent_folder:
        file_metadata.update(
            {
                "parents": (
                    parent_folder
                    if isinstance(parent_folder, list)
                    else [parent_folder]
                )
            }
        )

    return_fields = return_fields or DEFAULT_GDRIVE_FIELDS

    if debug_api:
        print({"file_metadata": file_metadata})

    file = (
        service.files()
        .create(body=file_metadata, media_body=media, fields=",".join(return_fields))
        .execute()
    )

    if debug_prn:
        print(f"Uploaded to Google Docs: {file['webViewLink']}")

    return file

# %% ../../nbs/google/classes/GDrive_v2.ipynb 13
async def replace_gfile(
    service: ga.Resource,
    media: MediaFileUpload,
    file_id: str,
    debug_prn: bool = False,
    return_fields: List[str] = None,
):
    return_fields = return_fields or DEFAULT_GDRIVE_FIELDS

    file = (
        service.files()
        .update(fileId=file_id, media_body=media, fields=",".join(return_fields))
        .execute()
    )

    if debug_prn:
        print(f"Replaced Google Docs: {file['webViewLink']}")

    return file

# %% ../../nbs/google/classes/GDrive_v2.ipynb 15
async def delete_gfile(
    service: ga.Resource,
    file_id: str,
    debug_prn: bool = False,
):

    file = service.files().delete(fileId=file_id).execute()

    if debug_prn:
        print(f"Deleted Google Docs: {file_id}")

    return True

# %% ../../nbs/google/classes/GDrive_v2.ipynb 17
class GDrive_ExportError(Exception):
    def __init__(self, doc_url):
        message = f"failure to download content for {doc_url}"
        super().__init__(message)


async def export_file_as(
    service: ga.Resource,
    file_id: str,
    export_mime_type: GDrive_MimeType_Enum,
    replace_folder: bool = True,
    export_fn: Callable = None,
    output_path: str = None,
):

    retry = 0
    content = None
    while retry < 3 and content is None:
        retry += 1
        try:
            content = (
                service.files()
                .export(fileId=file_id, mimeType=export_mime_type.value)
                .execute()
            )

        except HttpError as err:
            print(err)

        except Exception as e:
            print(e)

    if not content:
        raise GDrive_ExportError(file_id)

    if not export_fn:
        return content

    upsert_folder(output_path, replace_folder=replace_folder)

    return export_fn(content=content, output_folder=output_path)


async def export_as_pptx(service: ga.Resource, file_id: str, output_path: str):
    return await export_file_as(
        service=service,
        file_id=file_id,
        export_mime_type=GDrive_MimeType_Enum.pptx,
        export_fn=download_pptx,
        output_path=output_path,
    )


async def export_as_zip(service: ga.Resource, file_id: str, output_path: str):
    return await export_file_as(
        service=service,
        file_id=file_id,
        export_mime_type=GDrive_MimeType_Enum.zip_file,
        export_fn=download_zip,
        output_path=output_path,
    )


class ExportAs_Enum(Enum):
    pptx = partial(export_as_pptx)
    zip_file = partial(export_as_zip)

    @classmethod
    def _missing_(cls, value):
        value = value.lower()
        try:
            for member in cls:
                if member.name == value:
                    return member

        except:
            return cls.zip_file

        return cls.zip_file

# %% ../../nbs/google/classes/GDrive_v2.ipynb 21
@dataclass
class GDrive_Metadata:
    """
    Metadata management class for Google Drive files using composition pattern.

    This class manages the core metadata fields and provides methods for
    extracting and managing metadata. It's designed to be used as a component
    within other classes rather than through inheritance.
    """

    # Core required metadata fields
    id: str
    url: str
    name: str

    # Optional extended metadata fields
    title: str = field(default=None)
    link: str = field(default=None)
    doc_type: str = field(default=None)
    persona: str = field(default=None)
    topic: str = field(default=None)
    review_date: str = field(default=None)
    extracted_metadata: Dict[str, str] = field(default_factory=dict)

    # Extraction configuration
    extraction_patterns: Dict[str, str] = field(
        default_factory=lambda: {
            "doc_type": r"@doc_type[:\s]*([^\n\r]+)",
            "persona": r"@persona[:\s]*([^\n\r]+)",
            "topic": r"@topic[:\s]*([^\n\r]+)",
            "review_date": r"@review_date[:\s]*([^\n\r]+)",
        }
    )

    def __post_init__(self):
        """Initialize default values for title and link if not provided."""
        if not self.title:
            self.title = self.name
        if not self.link:
            self.link = self.url

    async def extract_from_content(
        self, content: str, update_instance: bool = True, **kwargs
    ) -> Dict[str, str]:
        """
        Extract metadata from text content using regex patterns.

        Args:
            content: Text content to extract metadata from
            update_instance: If True, updates the instance fields with extracted metadata
            **kwargs: Additional arguments for customization

        Returns:
            Dictionary containing extracted metadata
        """
        metadata = self.get_core_metadata()

        try:
            if content:
                # Extract metadata using regex patterns
                for key, pattern in self.extraction_patterns.items():
                    match = re.search(pattern, content, re.IGNORECASE)
                    if match:
                        metadata[key] = match.group(1).strip()

            # Always include basic metadata
            metadata["title"] = self.name
            metadata["link"] = self.url

            if update_instance:
                self.update_metadata(metadata)

        except Exception as e:
            print(f"Error extracting metadata: {e}")
            # Still return basic metadata even if extraction fails
            metadata.update(
                {
                    "title": self.name,
                    "link": self.url,
                    "doc_type": None,
                    "persona": None,
                    "topic": None,
                    "review_date": None,
                    "error": str(e),
                }
            )

            if update_instance:
                self.update_metadata(metadata)

        return metadata

    def get_core_metadata(self) -> Dict[str, str]:
        """
        Get the core required metadata fields.

        Returns:
            Dictionary containing id, url, and name
        """
        return {"id": self.id, "url": self.url, "name": self.name}

    def get_extended_metadata(self) -> Dict[str, str]:
        """
        Get all metadata fields including optional ones.

        Returns:
            Dictionary containing all metadata fields
        """
        return {
            "id": self.id,
            "url": self.url,
            "name": self.name,
            "title": self.title,
            "link": self.link,
            "doc_type": self.doc_type,
            "persona": self.persona,
            "topic": self.topic,
            "review_date": self.review_date,
        }

    def update_metadata(self, metadata: Dict[str, str]) -> None:
        """
        Update the instance metadata fields from a dictionary.

        Args:
            metadata: Dictionary containing metadata to update
        """
        for key, value in metadata.items():
            if hasattr(self, key):
                setattr(self, key, value)

        # Store the full metadata dictionary
        self.extracted_metadata = metadata.copy()

    def to_dict(self) -> Dict[str, str]:
        """
        Convert the metadata instance to a dictionary.

        Returns:
            Dictionary representation of all metadata
        """
        return self.get_extended_metadata()

    def set_custom_patterns(self, patterns: Dict[str, str]) -> None:
        """
        Set custom regex patterns for metadata extraction.

        Args:
            patterns: Dictionary mapping field names to regex patterns
        """
        self.extraction_patterns.update(patterns)

# %% ../../nbs/google/classes/GDrive_v2.ipynb 22
class GDrive_ServiceRequired(Exception):
    def __init__(self, doc_url=None, doc_id=None):
        message = f"service (googleclientapi.discovery.Resource) required to download {doc_url or doc_id}"
        super().__init__(message)


@dataclass
class GDrive_File:
    """
    Google Drive File class that uses composition with GDrive_Metadata.

    This class manages Google Drive files and delegates metadata operations
    to a GDrive_Metadata instance for better separation of concerns.
    """

    # Core file identification fields
    id: str
    name: str
    url: str
    modified_time: dt.datetime

    # Google Drive specific fields
    auth: ga.GoogleAuth = field(repr=False)
    mime_str: str
    service: Any = field(repr=False, default=None)
    gdrive_mime_type: GDrive_MimeType_Enum = None
    parent_ls: List[str] = field(default=None)
    file: Any = field(repr=False, default=None)

    content: Any = None

    # Metadata composition - contains a GDrive_Metadata instance
    metadata: GDrive_Metadata = field(default=None, repr=False)

    def __post_init__(self):
        if not self.service:
            self.service = self.auth.generate_gdrive_service()

        try:
            self.gdrive_mime_type = GDrive_MimeType_Enum(self.mime_str)

        except Exception as e:
            print(e)

        self._init_metadata()

    def _init_metadata(self):
        if not self.metadata:
            self.metadata = GDrive_Metadata(id=self.id, url=self.url, name=self.name)

    async def export_content(
        self, export_fn: Callable = None, output_path: str = "./EXPORT/"
    ):
        # Export the file as plain text to extract metadata
        content = await export_file_as(
            service=self.service,
            file_id=self.id,
            export_mime_type=GDrive_MimeType_Enum.txt,
            export_fn=export_fn,  # Return raw content
            output_path=output_path,
        )

        if isinstance(content, bytes):
            content = content.decode("utf-8")

        self.content = content

        return content

    async def extract_metadata(
        self,
        update_instance: bool = True,
        output_path: str = "./EXPORT/",
        export_fn: Callable = None,
    ) -> Dict[str, str]:
        """
        Extract metadata from the Google Drive file content.

        Uses the composition pattern to delegate metadata extraction to the
        GDrive_Metadata component.

        Args:
            update_instance: If True, updates the metadata component with extracted data

        Returns:
            Dictionary containing extracted metadata
        """
        try:
            # Export the file as plain text to extract metadata
            content = self.content or self.export_content(
                export_fn=export_fn, output_path=output_path  # Return raw content
            )

            # Delegate to metadata component
            return await self.metadata.extract_from_content(
                content=content, update_instance=update_instance
            )

        except Exception as e:
            print(f"Error extracting metadata from {self.id}: {e}")
            # Return basic metadata through metadata component
            basic_metadata = self.metadata.get_core_metadata()
            basic_metadata.update(
                {
                    "title": self.name,
                    "link": self.url,
                    "doc_type": None,
                    "persona": None,
                    "topic": None,
                    "review_date": None,
                    "error": str(e),
                }
            )

            if update_instance:
                self.metadata.update_metadata(basic_metadata)

            return basic_metadata

    @classmethod
    def _from_json(
        cls,
        gdrive_obj: dict,
        auth: ga.GoogleAuth,
        service: ga.Resource = None,
        **kwargs,
    ):
        gfile = cls(
            # Core fields
            id=gdrive_obj["id"],
            url=str(gdrive_obj["webViewLink"]),
            name=gdrive_obj["name"],
            modified_time=convert_str_to_date(gdrive_obj["modifiedTime"]),
            # GDrive_File specific fields
            auth=auth,
            service=service or auth.generate_gdrive_service(),
            file=gdrive_obj,
            mime_str=gdrive_obj["mimeType"],
            parent_ls=gdrive_obj.get("parent_ls") or gdrive_obj.get("parents"),
            **kwargs,
        )

        return gfile

    @classmethod
    async def get_by_id(
        cls,
        id: str,
        fields: List[str] = None,
        auth: ga.GoogleAuth = None,
        service: ga.Resource = None,
        return_raw: bool = False,
    ):
        fields = fields or DEFAULT_GDRIVE_FIELDS

        service = service or auth.generate_gdrive_service()

        file = await drive_routes.get_file_by_id(
            file_id=id, service=service, fields=fields
        )

        if return_raw:
            return file

        return cls._from_json(gdrive_obj=file, auth=auth, service=service)

    async def export(self, output_path: str, export_mime_type: GDrive_MimeType_Enum):

        if isinstance(export_mime_type, GDrive_MimeType_Enum):
            export_mime_type = export_mime_type.name

            return await ExportAs_Enum(export_mime_type).value(
                service=self.service, file_id=self.id, output_path=output_path
            )

        return await ExportAs_Enum(export_mime_type).value(
            service=self.service, file_id=self.id, output_path=output_path
        )

    @classmethod
    async def create(
        cls,
        media: MediaFileUpload,
        file_name: str,
        auth: ga.GoogleAuth = None,
        service: ga.Resource = None,
        mime_type_enum: GDrive_MimeType_Enum = GDrive_MimeType_Enum.gdoc,
        debug_prn: bool = False,
        parent_folder: str = None,
        debug_api: bool = False,
    ):

        res = await create_gfile(
            service=service or auth.generate_gdrive_service(),
            media=media,
            file_name=file_name,
            mime_type_enum=mime_type_enum,
            debug_prn=debug_prn,
            parent_folder=parent_folder,
            debug_api=debug_api,
        )

        return cls._from_json(gdrive_obj=res, auth=auth, service=service)

    async def update(
        self,
        media: MediaFileUpload,
        debug_prn: bool = False,
    ):

        res = await replace_gfile(
            service=self.service or self.auth.generate_gdrive_service(),
            file_id=self.id,
            media=media,
            debug_prn=debug_prn,
        )

        self.__dict__.update(res)

        return self

    async def delete(self, debug_prn: bool = False):
        return await delete_gfile(
            service=self.service or self.auth.generate_gdrive_service(),
            file_id=self.id,
            debug_prn=debug_prn,
        )

# %% ../../nbs/google/classes/GDrive_v2.ipynb 25
@dataclass
class Custom_Document_Metadata(GDrive_Metadata):
    """
    Example custom metadata extractor that extends GDrive_Metadata.

    This demonstrates how to create custom metadata extractors by extending
    the base GDrive_Metadata class with custom extraction logic.
    """

    # Custom fields specific to this implementation
    content_source: str = field(default=None)
    extraction_method: str = field(default="custom_regex")

    def __post_init__(self):
        super().__post_init__()
        # Set custom extraction patterns
        self.extraction_patterns.update(
            {
                "doc_type": r"(?:Document Type|Type):\s*([^\n\r]+)",
                "persona": r"(?:Target Audience|Audience):\s*([^\n\r]+)",
                "topic": r"(?:Subject|Topic):\s*([^\n\r]+)",
                "review_date": r"(?:Review Date|Next Review):\s*([^\n\r]+)",
            }
        )

    async def extract_from_content(
        self, content: str = None, update_instance: bool = True, **kwargs
    ) -> Dict[str, str]:
        """
        Custom implementation of metadata extraction with enhanced patterns.

        Args:
            content: Text content to extract metadata from
            update_instance: If True, updates the instance fields with extracted metadata
            **kwargs: Additional arguments for customization

        Returns:
            Dictionary containing extracted metadata
        """
        # Call parent method to do basic extraction
        metadata = await super().extract_from_content(
            content, update_instance=False, **kwargs
        )

        try:
            if content:
                # Add custom metadata fields
                metadata["extraction_method"] = self.extraction_method
                metadata["content_source"] = self.content_source or "manual_input"

                # Custom processing could go here
                # e.g., API calls, ML models, etc.

            if update_instance:
                self.update_metadata(metadata)

        except Exception as e:
            print(f"Error in custom metadata extraction: {e}")
            metadata["error"] = str(e)

        return metadata


# Example of using custom metadata in a file object
@dataclass
class Enhanced_GDrive_File(GDrive_File):
    """Example of using custom metadata component in composition."""

    def __post_init__(self):
        # Don't call parent __post_init__ yet
        self._parent_init()

        # Initialize with custom metadata component
        self.metadata = Custom_Document_Metadata(
            id=self.id, url=self.url, name=self.name, content_source="gdrive_export"
        )

# %% ../../nbs/google/classes/GDrive_v2.ipynb 34
@dataclass
class GDrive_Folder(GDrive_File):

    gdrive_files: List[GDrive_File] = field(default=None)

    async def _get_child_from_json(self, child, is_recursive: bool = True):
        if not child.get("is_folder"):
            g_child = GDrive_File._from_json(
                gdrive_obj=child, service=self.service, auth=self.auth
            )

        if child.get("is_folder"):
            g_child = GDrive_Folder._from_json(
                gdrive_obj=child, service=self.service, auth=self.auth
            )

            if is_recursive:
                await g_child.get_files(is_recursive=is_recursive)

        if not self.gdrive_files:
            self.gdrive_files = []

        self.gdrive_files.append(g_child)

    async def get_files(self, is_recursive: bool = True, return_raw: bool = False):

        folder_obj = await walk_folder(
            service=self.service,
            folder_id=self.id,
            is_recursive=is_recursive,
        )

        if return_raw:
            return folder_obj

        await ce.gather_with_concurrency(
            *[
                self._get_child_from_json(child=child, is_recursive=is_recursive)
                for child in folder_obj.get("children")
            ],
            n=10,
        )

        return self.gdrive_files

    async def extract_metadata_from_all_files(
        self, file_types: List[str] = None, max_concurrent: int = 5
    ) -> List[Dict[str, str]]:
        """
        Extract metadata from all files in the folder and subfolders.

        Args:
            file_types: List of mime types to process (e.g., ['application/vnd.google-apps.document'])
                       If None, processes all supported document types
            max_concurrent: Maximum number of concurrent metadata extractions

        Returns:
            List of dictionaries containing metadata for each file
        """
        if not self.gdrive_files:
            await self.get_files(is_recursive=True)

        # Default to Google Docs and Sheets if no file types specified
        if file_types is None:
            file_types = [
                "application/vnd.google-apps.document",
                "application/vnd.google-apps.spreadsheet",
                "application/vnd.google-apps.presentation",
            ]

        # Collect all files that match the specified types, including from subfolders
        files_to_process = []

        def collect_files(file_list):
            for gfile in file_list:
                if isinstance(gfile, GDrive_Folder):
                    if gfile.gdrive_files:
                        collect_files(gfile.gdrive_files)
                elif gfile.mime_str in file_types:
                    files_to_process.append(gfile)

        collect_files(self.gdrive_files)

        print(f"Found {len(files_to_process)} files to process for metadata extraction")

        # Extract metadata concurrently
        async def extract_single_metadata(gfile):
            try:
                metadata = await gfile.extract_metadata(update_instance=True)
                metadata["file_id"] = gfile.id
                metadata["mime_type"] = gfile.mime_str
                return metadata
            except Exception as e:
                print(f"Error extracting metadata from {gfile.name}: {e}")
                return {
                    "file_id": gfile.id,
                    "title": gfile.name,
                    "link": gfile.url,
                    "mime_type": gfile.mime_str,
                    "doc_type": None,
                    "persona": None,
                    "topic": None,
                    "review_date": None,
                    "error": str(e),
                }

        metadata_results = await ce.gather_with_concurrency(
            *[extract_single_metadata(gfile) for gfile in files_to_process],
            n=max_concurrent,
        )

        return metadata_results

# %% ../../nbs/google/classes/GDrive_v2.ipynb 37
import json
import csv
from pathlib import Path


async def export_metadata_to_file(
    metadata_list: List[Dict[str, str]], output_path: str, format: str = "csv"
) -> str:
    """
    Export metadata to CSV or JSON file.

    Args:
        metadata_list: List of metadata dictionaries from extract_metadata_from_all_files
        output_path: Path where to save the file
        format: 'csv' or 'json'

    Returns:
        Path to the created file
    """
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    if format.lower() == "csv":
        if not str(output_path).endswith(".csv"):
            output_path = output_path.with_suffix(".csv")

        with open(output_path, "w", newline="", encoding="utf-8") as csvfile:
            if metadata_list:
                fieldnames = metadata_list[0].keys()
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                writer.writerows(metadata_list)

    elif format.lower() == "json":
        if not str(output_path).endswith(".json"):
            output_path = output_path.with_suffix(".json")

        with open(output_path, "w", encoding="utf-8") as jsonfile:
            json.dump(metadata_list, jsonfile, indent=2, ensure_ascii=False)

    else:
        raise ValueError("Format must be 'csv' or 'json'")

    return str(output_path)
