/*
 * RiskScape™ Copyright New Zealand Institute for Earth Science Limited
 * (Earth Sciences New Zealand) is distributed for research purposes only
 * under the terms of AGPLv3.
 *
 * RiskScape™ Copyright 2026 New Zealand Institute for Earth Science
 * Limited (Earth Sciences New Zealand). All rights reserved. Source code
 * available under the AGPLv3.
 * 
 * This program is free software: you can redistribute it and/or modify it under
 *  the terms of the GNU Affero General Public License as published by the Free
 *  Software Foundation, either version 3 of the License, or (at your option) any
 *  later version.
 * 
 * This program is distributed for RESEARCH PURPOSES ONLY, in the hope that it will
 * be useful for research and education initiatives.
 * 
 * If you are not a researcher, or you are a researcher who wishes to use this
 * program on terms other than AGPLv3 (including those who wish to restrict the
 * distribution of any source code created using this program), please contact:
 * https://riskscape.org.nz
 * 
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Affero General Public License for more details.  You should have received a copy
 * of the GNU Affero General Public License along with this program.  If not, see
 * <http://www.gnu.org/licenses/>.
 * 
 * By way of summary only, under the AGPLv3:
 *     • Permissions of this strongest copyleft license are conditioned
 *       on making available complete source code of licensed works and
 *       modifications, which include larger works using a licensed work,
 *       under the same license.
 *     • Copyright and license notices must be preserved.
 *     • Contributors provide an express grant of patent rights.
 *     • When a modified version is used to provide a service over a
 *       network, the complete source code of the modified version must be made
 *       available.
 */
package nz.org.riskscape.engine.problem;

import java.net.URI;
import java.util.Arrays;
import java.util.List;

import com.google.common.collect.Range;

import nz.org.riskscape.engine.Identified;
import nz.org.riskscape.engine.i18n.Message;
import nz.org.riskscape.engine.problem.Affecting.Target;
import nz.org.riskscape.engine.resource.Resource;
import nz.org.riskscape.problem.Problem;
import nz.org.riskscape.problem.Problems;
import nz.org.riskscape.problem.StandardCodes;

/**
 * General purpose problems that can appear in many parts of the system.  These are an alternative to, and an eventual
 * replacement for, problems coded up with {@link StandardCodes}.
 *
 */
public interface GeneralProblems extends ProblemFactory {

  static GeneralProblems get() {
    return Problems.get(GeneralProblems.class);
  }

  /**
   * Convenience that will return {@link #noSuchObject(java.lang.String, java.lang.Class, java.util.List) }
   * if simiarlIds is not empty else {@link #noSuchObjectExists(java.lang.String, java.lang.Class) }.
   *
   * The prevents the user being given the did you mean one of error with an empty list of options which
   * may be quite misleading.
   */
  static Problem noSuchObjectExists(String id, Class<?> clazz, List<String> similarIds) {
    if (similarIds == null || similarIds.isEmpty()) {
      return get().noSuchObjectExists(id, clazz);
    }
    return get().noSuchObjectExistsDidYouMean(id, clazz, similarIds);
  }

  /**
   * Produces error: '`name`' `typeOfThing` was required but not found.
   * This is a helper for when the required object doesn't actually exist yet.
   */
  static Problem required(String name, Class<?> typeOfThing) {
    return get().required(ProblemPlaceholder.of(typeOfThing, name));
  }

  /**
   * Produces error: '`thing`' was required but not found
   */
  Problem required(Object thing);

  /**
   * Produces an error for when a user to tries to add an object with an id that is already used, e.g. adding
   * a function with the same id twice.
   */
  Problem objectAlreadyExists(String id, Class<?> identifiedClass);

  /**
   * Produces an error for when a user has referenced an object by its id and it doesn't exist
   */
  Problem noSuchObjectExists(@Affecting(Target.NAME) String id, @Affecting(Target.CLASS) Class<?> clazz);

  Problem noSuchObjectExistsDidYouMean(
    @Affecting(Target.NAME) String id,
    @Affecting(Target.CLASS) Class<?> clazz,
    List<String> similarIds
  );

  /**
   * Produces an error for when a user has referenced and object by its id, but it has errors associated with it
   * that mean it can't be used.  It's expected that the caller of this constructor append the failures to this
   * problem
   */
  Problem failedResourceLookedUp(String id, URI location, Class<?> failedClass);

  /**
   * Version of {@link #failedResourceLookedUp(String, Resource, Class)} where no resource (or the unknown resource)
   * is available.
   */
  Problem failedObjectLookedUp(String id, Class<?> failedClass);

  /**
   * @return a problem suitable for wrapping other problems caused by trying to build a type of thing from
   * a resource
   */
  Problem badResource(Class<?> expectedType, URI location);

  /**
   * '<value>' is not a valid option for <context>. Available options are: <list>'.
   */
  Problem notAnOption(String value, Object context, List<?> possibleOptions);

  /**
   * When value is not a valid option. The similar options may be subset of those available.
   * 'value' is not a valid option. Did you mean one of: <list>'
   */
  Problem notAnOptionSimilarTo(String value, List<?> similarOptions);

  /**
   * For when an operation is called despite not being supported.
   * @param operation up-supported operation that was called
   * @param clazz     the class that does not support the operation
   */
  Problem operationNotSupported(String operation, Class clazz);

  /**
   * Convenience version of {@link #notAnOption(String, Object, List)} for enums.
   * Useful when a user supplied string doesn't match any of the values from the given enum
   */
  static <T extends Enum<?>> Problem notAnOption(String value, Class<T> enumClass) {
    return get().notAnOption(value, enumClass, Arrays.asList(enumClass.getEnumConstants()));
  }

  /**
   * A problem suitable for wrapping a more specific parameter binding error
   */
  Problem couldNotConvert(String parameterName, Class<?> parameterType, String invalidValue);

  /**
   * Failed to construct <thing> from the input provided
   */
  Problem failedToBuild(Object thing);

  /**
   * When an {@link Identified} object has failed validation
   */
  Problem failedToValidate(Class<? extends Identified> kindOfThing, String id, Resource resource);

  /**
   * A list was provided, but it was expected to contain a certain number of elements
   */
  Problem badListLength(String expectedNumber, int actual);

  @Message("The lists must contain the same number of items, but the {0} list has {1} items and "
      + "the {2} list has {3} items")
  Problem differentListLengths(Object thing1, int length1, Object thing2, int length2);

  /**
   * When thing has be deprecated but is used.
   * @param thing that has been deprecated
   * @param replacement the replacement for thing
   */
  @SeverityLevel(Problem.Severity.WARNING)
  Problem deprecated(Object thing, Object replacement);

  /**
   * Useful non-specific method to group a bunch of problems when there's no specific parent issue.
   * Use {@link Problems#toSingleProblem(List)} instead of this method.
   * FYI We could avoid this kind of nonsense if we had a ProblemList type that abstracted over this, a bit like a
   * NodeList in some xml libs
   */
  Problem multipleProblems();

  @SeverityLevel(Problem.Severity.WARNING)
  Problem precisionLoss(Number from, Number to);

  /**
   * A given value was outside of an expected range.
   *
   * Could be a parameter or a function argument or any other place that user input is accepted, so
   * this problem lives here rather than in one of the specific factories
   */
  <T extends Comparable<?>> Problem valueOutOfRange(Object affected, T value, Range<T> range);

  /**
   * "Value '<value-provided>' for <named-thing> is invalid, it must be <some-constraint>"
   * E.g. "Value '0' for 'num-event-sets' parameter is invalid, it must be > 0"
   */
  Problem badValue(Object value, Object thing, Object constraint);

  @Message("Wrong number of {0}(s) for {1} - expected {2}, but found {3}")
  Problem badArity(Class<?> arityThing, Object parent, Range<Integer> expected, int actual);

  @Message("''{0}'' is already used by {1} so it cannot be used as a {2} name")
  Problem nameAlreadyUsedBy(String id, Object usedBy, Class<?> thing);
}
