# SPDX-FileCopyrightText: Copyright © 2025 Idiap Research Institute <contact@idiap.ch>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Visualization CLI for demographically fair machine learning systems.

This module defines the ``pareto`` command, which loads one or more system solutions in
JSON format and computes the pareto fronts, with options to generate visualization plots.

The CLI provides options to output formatting, and plot generation, and is
intended to support analysis of fairness-performance trade-offs across systems.
"""

import logging
import pathlib

logger = logging.getLogger(__name__.split(".", 1)[0])

import click
import matplotlib

# avoids loading X11-dependent backends
matplotlib.use("agg")

from ..solutions import Solutions
from . import utils


@click.command(
    epilog="""Examples:

\b
  1. Evaluate adjustable fairness and display the pareto front in table format on the terminal

     .. code:: sh

        fairical pareto system-1.json system-2.json

    2. Evaluate adjustable fairness display the pareto front in table format on the terminal,
       and generate a plot showing the dominated and non-dominated solutions.

     .. code:: sh

        fairical pareto system-1.json system-2.json --plot
""",
)
@click.argument(
    "system",
    type=click.Path(exists=True, dir_okay=False, readable=True, path_type=pathlib.Path),
    required=True,
    nargs=-1,
    callback=utils.validate_solutions_json,
)
@click.option(
    "-d/-D",
    "--deduplicate/--no-deduplicate",
    default=True,
    help="If we should prune (deduplicate) similar solutions.",
)
@click.option(
    "-p",
    "--plot",
    is_flag=True,
    default=False,
    help="If we should plot the solutions",
)
@click.option(
    "--hide-ds",
    is_flag=True,
    default=False,
    help="If true, hide the ds points for a-priori data from the plot.",
)
@click.option(
    "-o",
    "--output-path",
    type=click.Path(
        dir_okay=True, file_okay=False, writable=True, path_type=pathlib.Path
    ),
    help="Directory where to store system solutions.",
)
@utils.verbosity_option(logger)
def pareto(
    system: dict[str, Solutions],
    deduplicate: bool,
    plot: bool,
    hide_ds: bool,
    output_path: pathlib.Path | None,
    verbose: int,
) -> None:  # numpydoc ignore=PR01
    """Compute pareto frontiers of systems.

    This command takes an arbitrary number of JSON files describing ML system solutions
    and displays the Pareto front (i.e. non-dominated solutions according to an
    arbitrary number of utility and demographic fairness metrics) on the terminal.

    A scatter plot showing the dominated and non-dominated solutions can be created
    by passing the `--plot` flag.

    The JSON files must be formatted as described in the documentation.
    """
    from fairical.plot import pareto_plot

    if output_path is None:
        output_path = pathlib.Path()

    if deduplicate:
        for k, v in system.items():
            logger.info(f"Deduplicating solutions from {k}")
            system[k] = v.deduplicate()

    solutions = {k: v.non_dominated_solutions() for k, v in system.items()}
    if plot:
        fig, _ = pareto_plot(solutions, hide_ds=hide_ds)

        output_file = output_path / "pareto.pdf"
        logger.info(f"Saving plot at `{output_file}`...")
        utils.prepare_and_backup(output_file)
        fig.savefig(output_file)

    for label, (nds, _) in solutions.items():
        table = nds.tabulate()

        print(f"\nPareto front for {label}")
        print(table)
