#!/usr/bin/env python3
# (C) 2026 Cadence Design Systems, Inc. (Cadence)
# All rights reserved.
# TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
# provided to current licensees or subscribers of Cadence products or
# SaaS offerings (each a "Customer").
# Customer is hereby permitted to use, copy, and modify the Sample Code,
# subject to these terms. Cadence claims no rights to Customer's
# modifications. Modification of Sample Code is at Customer's sole and
# exclusive risk. Sample Code may require Customer to have a then
# current license or subscription to the applicable Cadence offering.
# THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED.  CADENCE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall Cadence be
# liable for any damages or liability in connection with the Sample Code
# or its use.

"""Converts a CSV or SDF file into an HTML file with molecular depictions."""

import argparse
import os
import pathlib
import sys
import typing

from openeye import oechem, oedepict
from rich_argparse import HelpPreviewAction, RichHelpFormatter

__SCRIPT_NAME__ = pathlib.Path(__file__).absolute().stem
__SCRIPT_DESC__ = "Convert CSV or SDF files into HTML with molecular depictions"
__SCRIPT_TOOLKITS__ = ["oechem", "oedepict"]
__SCRIPT_CATEGORIES__ = ["depiction"]


def parse_options() -> argparse.Namespace:
    """Set up command line options."""
    parser = argparse.ArgumentParser(
        add_help=True,
        formatter_class=RichHelpFormatter,
        description="[yellow]" + __SCRIPT_DESC__ + "[/yellow]",
    )

    io_group = parser.add_argument_group("Input/output options")
    io_group.add_argument(
        "--mol",
        metavar="MOL-FILE",
        type=str,
        required=True,
        help="input molecule file (.csv or .sdf)",
    )
    io_group.add_argument(
        "--html",
        metavar="HTML-FILE",
        type=str,
        required=True,
        help="output HTML file (.html)",
    )

    parser.add_argument("--help-image", action=HelpPreviewAction)
    parser.add_argument(
        "--save-console-svg",
        default=False,
        action="store_true",
        help=f"run command and capture console output in {__SCRIPT_NAME__}.svg file",
    )
    return parser.parse_args()


def main() -> int:
    """Convert molecule file to HTML with molecular depictions."""
    args = parse_options()

    html_file = pathlib.Path(args.html)
    if html_file.suffix.lower() != ".html":
        oechem.OEThrow.Fatal("Invalid file extension expected .html!")

    mol_list: list[oechem.OEMolBase] = read_molecules(args.mol)
    tags = collect_data_tags(mol_list)

    width, height = 200, 200
    opts = oedepict.OE2DMolDisplayOptions(width, height, oedepict.OEScale_AutoScale)

    with html_file.open("w") as ofp:
        _write_html_file(ofp, mol_list, pathlib.Path(args.mol).name, tags, opts)

    return os.EX_OK


def read_molecules(mol_filename: str) -> list[oechem.OEMolBase]:
    """Read molecules from a CSV or SDF file and return a list of OEMolBase objects."""
    mol_path = pathlib.Path(mol_filename)
    if not mol_path.exists():
        oechem.OEThrow.Fatal(f"Cannot open input file '{mol_path.name}'!")

    ifs = oechem.oemolistream()
    if not ifs.open(str(mol_path)):
        oechem.OEThrow.Fatal(f"Cannot open input file '{mol_path.name}'!")

    if ifs.GetFormat() not in [oechem.OEFormat_CSV, oechem.OEFormat_SDF]:
        oechem.OEThrow.Fatal("Input must be a CSV or SDF file!")
    mol_list: list[oechem.OEMolBase] = [
        oechem.OEGraphMol(m) for m in ifs.GetOEGraphMols()
    ]
    return mol_list


def collect_data_tags(mol_list: list[oechem.OEMolBase]) -> list[str]:
    """Collect all unique SD data tags from a list of molecules."""
    tags: list[str] = []
    for mol in mol_list:
        for dp in oechem.OEGetSDDataIter(mol):
            if dp.GetTag() not in tags:
                tags.append(dp.GetTag())
    return tags


def _write_html_file(
    ofp: typing.TextIO,
    mol_list: list[oechem.OEMolBase],
    filename: str,
    tags: list[str],
    opts: oedepict.OE2DMolDisplayOptions,
) -> None:
    """Write the full HTML file content."""
    _write_html_header(ofp, filename, tags)
    for mol in mol_list:
        _write_html_table_row(ofp, mol, opts, tags)
    _write_html_footer(ofp)


def _write_html_table_row(
    ofp: typing.TextIO,
    mol: oechem.OEMolBase,
    opts: oedepict.OE2DMolDisplayOptions,
    tags: list[str],
) -> None:
    """Write one table row with the molecule image and its SD data."""
    ofp.write("<tr class=row>\n")
    ofp.write(f"<td> {_get_svg_image(mol, opts)}\n </td>\n")
    for tag in tags:
        value = oechem.OEGetSDData(mol, tag) if oechem.OEHasSDData(mol, tag) else "N/A"
        ofp.write(f"<td> {value} </td>")
    ofp.write("</tr>\n")


def _get_svg_image(mol: oechem.OEMolBase, opts: oedepict.OE2DMolDisplayOptions) -> str:
    """Generate a bare SVG image string for a molecule."""
    oedepict.OEPrepareDepiction(mol)
    disp = oedepict.OE2DMolDisplay(mol, opts)
    image_str = oedepict.OERenderMoleculeToString("bsvg", disp, False)
    return image_str.decode("utf-8")


def _write_html_header(ofp: typing.TextIO, filename: str, tags: list[str]) -> None:
    """Write the HTML header including CSS styles and table header row."""
    table_width = min(1800, (len(tags) + 1) * 200)
    ofp.write("<style type='text/css'>\n")
    ofp.write(
        "h1                          { text-align:center; border-width:thick; border-style:double;border-color:#BCC; }\n"
    )
    ofp.write(
        f"table.csv                   {{ border-spacing:1; background: #FFF; width:{table_width}px; }}\n"
    )
    ofp.write(
        "table.csv td, th            { width:100px; text-align:center; padding:3px 3px 3px 3px; }\n"
    )
    ofp.write(
        "table.csv th                { height:50px; color:#FFF; background:#788; }\n"
    )
    ofp.write("table.csv tr:nth-child(even){ color:#000; background:#FFE; }\n")
    ofp.write("table.csv tr:nth-child(odd) { color:#000; background:#FEF; }\n")
    ofp.write("table.csv tr:hover          { color:#000; background:#DDD; }\n")
    ofp.write("</style>\n")
    ofp.write(f"<html><h1>{filename}</h1>\n")
    ofp.write("<body>\n")
    ofp.write("<table class=csv>\n")
    ofp.write("<tbody>\n")
    ofp.write("<tr>\n")
    ofp.write("<th> Molecule</th>")
    ofp.writelines(f"<th> {tag} </th>" for tag in tags)
    ofp.write("\n</tr>\n")


def _write_html_footer(ofp: typing.TextIO) -> None:
    """Write the HTML closing tags."""
    ofp.write("</table>\n</body>\n</html>\n")


setattr(main, "__SCRIPT_NAME__", __SCRIPT_NAME__)
setattr(main, "__SCRIPT_DESC__", __SCRIPT_DESC__)
setattr(main, "__SCRIPT_TOOLKITS__", __SCRIPT_TOOLKITS__)
setattr(main, "__SCRIPT_CATEGORIES__", __SCRIPT_CATEGORIES__)

if __name__ == "__main__":
    sys.exit(main())
