"""
Create a table off contents file in index.rst that references all of the OmniGraph node generated
documentation files that live in that directory.

This processing is highly tied to the formatting of the OGN generated documentation files so if they
change this has to as well.

The table of contents will be in two sections.

    A table consisting of columns with [node name, node version, link to node doc file, link to node appendix entry]
    An appendix with headers consisting of the node name and body consisting of the node's description
"""
import argparse
import logging
import os
from pathlib import Path
from typing import List, Optional

from .generate_documentation import (
    RE_OGN_BODY_MARKER,
    RE_OGN_DESCRIPTION_TITLE,
    RE_OGN_DOC_FILENAME,
    RE_OGN_INPUTS_TITLE,
    RE_OGN_NAME_INFO,
)
from .utils import WritableDir, logger, rst_table, rst_title

# If True then perform more aggressive directory checks, not safe in a multi-threaded environment
SAFE_DIRECTORY_CREATION = False

# Name of the generated index file
INDEX_FILENAME = "index.rst"

# Selectively turn on logging if the OGN debugging environment variable is set
logger.setLevel(logging.DEBUG if os.getenv("OGN_DEBUG") else logging.WARN)


# ======================================================================
def construct_parser() -> argparse.ArgumentParser:
    """Construct and return the parser for the script arguments"""

    # If no output directory is specified generated files will end up in the current directory
    default_output_dir = Path.cwd()

    # This helps format the usage information in a nicer way
    os.putenv("COLUMNS", "120")

    # Construct the parsing information. Run the script with "--help" to see the usage.
    parser = argparse.ArgumentParser(
        description="Read a directory of OGN documentation files and create an index for them",
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument(
        "-od",
        "--ognDirectory",
        action=WritableDir,
        const=default_output_dir,
        type=Path,
        metavar="DIR",
        help="directory containing the OGN documentation files, where the index will be generated",
    )
    parser.add_argument("-v", "--verbose", action="store_true", help="output the steps the script is performing")
    return parser


# ======================================================================
class OgnIndexCreator:
    """Handler to analyze OGN documentation files and generate a Table of Contents for them

    Attributes:
        documentation_files: List of documentation files found in the directory
        index_file_path: Full path to the generated index file
        ogn_directory: Path where the table of contents will be generated
    """

    def __init__(self, ogn_directory: Path):
        """Read the contents of the docs directory and prepare it for generation"""
        logger.info("Scanning documentation directory %s", ogn_directory)
        self.ogn_directory = ogn_directory
        self.index_file_path = self.ogn_directory / INDEX_FILENAME
        self.documentation_files = []
        for path_object in self.ogn_directory.glob("**/*"):
            if path_object.is_file() and RE_OGN_DOC_FILENAME.search(str(path_object)):
                self.documentation_files.append(path_object)

    # ----------------------------------------------------------------------
    def extract_node_information(self, ogn_doc_path: str):
        """Read the OGN documentation file and extract the information to use for the index

        Patterns assumed, in order:
            One line matching RE_OGN_NAME_INFO with the node name
            One line matching RE_OGN_DESCRIPTION_TITLE, followed immediately by...
            ...one line with the title RST (probably dashes)
            An undefined number of lines containing the node description
            A blank line (to be omitted)
            One line matching RE_OGN_INPUTS_TITLE, marking the end of the description
            The rest will be ignored as there is no more relevant information

        Args:
            ogn_doc_path: Path to the node's documentation file

        Returns:
            (Marker, Name, Description) tuple with the node's information
        """
        in_body = False
        name = None
        description = []
        marker = None

        found_description = False  # True after the description title was found
        in_description = False  # True after the description body is entered

        try:
            with open(ogn_doc_path, "r", encoding="utf-8") as doc_fd:
                for line in doc_fd:
                    if not in_body:
                        body_marker_match = RE_OGN_BODY_MARKER.match(line)
                        if body_marker_match:
                            in_body = True
                            marker = body_marker_match.group(1)
                    elif name is None:
                        name_match = RE_OGN_NAME_INFO.match(line)
                        if name_match:
                            name = name_match.group(1)
                    elif found_description:
                        found_description = False
                        in_description = True
                    elif in_description:
                        if RE_OGN_INPUTS_TITLE.search(line):
                            in_description = False
                            break
                        description.append(line[:-1])
                    elif RE_OGN_DESCRIPTION_TITLE.search(line):
                        found_description = True

            # If attributes were not found then an extra blank line is needed to separate sections
            if in_description:
                description.append("\n")

            if marker is None:
                logger.error("Marker not found in %s", ogn_doc_path)
            if name is None:
                logger.error("Name not found in %s", ogn_doc_path)
            if not description:
                logger.error("Description not found in %s", ogn_doc_path)
        except Exception as error:  # noqa: PLW0703
            # Report the failure but continue processing
            logger.error("Error processing %s: %s", ogn_doc_path, error)

        return (marker, name.rstrip(), description)

    # ----------------------------------------------------------------------
    def index_is_out_of_date(self) -> bool:
        """Returns True if the index file is older than any of the other files in the directory"""
        if not self.index_file_path.is_file():
            return True
        index_modified_time = self.index_file_path.lstat().st_mtime
        return any(
            index_modified_time < documentation_file.lstat().st_mtime for documentation_file in self.documentation_files
        )

    # ----------------------------------------------------------------------
    def construct_index(self):
        """Construct the table of contents in an index file"""
        if not self.index_is_out_of_date():
            logger.info("Documentation is up to date. Index generation skipped")
            return

        # Dictionary containing the information needed to generate the index file
        # Key = Node File, Value = [Name, Version, Description]
        node_information = {}
        for ogn_doc_file in self.documentation_files:
            logger.info("Processing %s", ogn_doc_file)
            node_information[ogn_doc_file] = self.extract_node_information(ogn_doc_file)

        sorted_keys = sorted(node_information.keys(), key=lambda key: node_information[key][1])

        rows = [["Node", "Detailed Documentation"]]
        if not node_information:
            # Avoid a table with no contents, as that will generate a syntax error
            rows.append(["", ""])
        else:
            for ogn_doc_file in sorted_keys:
                (marker, node_name, _) = node_information[ogn_doc_file]
                rows.append([f"`{node_name}`_", f":ref:`{marker}`"])

        try:
            with open(self.index_file_path, "w", newline="\n", encoding="utf-8") as index_file:
                index_file.write(rst_title("OGN Node List", 0))
                index_file.write("\n\n.. tabularcolumns:: |l|l|\n\n")
                index_file.write(rst_table(rows))
                index_file.write("\n")
                index_file.write(rst_title("Node Descriptions", 1))
                index_file.write("\n")
                for ogn_doc_file in sorted_keys:
                    (_, node_name, node_documentation) = node_information[ogn_doc_file]
                    index_file.write(f"{rst_title(node_name, 1)}\n")
                    index_file.write("\n".join(node_documentation))
        except Exception as error:  # noqa: PLW0703
            logger.error("Cannot write to index file %s : %s", self.index_file_path, error)


# ======================================================================
def main_docs(args_to_parse: Optional[List] = None):
    """Parse the contents of sys.args and perform the requested function."""

    parser = construct_parser()
    args = parser.parse_args(args_to_parse)

    # If the script steps are to be echoed enable the logger and dump the script arguments as a first step
    logger.setLevel(logging.DEBUG if args.verbose else logging.WARN)
    logger.info("ognDirectory = %s", args.ognDirectory)

    index_handler = OgnIndexCreator(args.ognDirectory)
    index_handler.construct_index()


# ======================================================================
if __name__ == "__main__":
    main_docs()
