/*
 * 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.wizard;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import nz.org.riskscape.engine.Identified;
import nz.org.riskscape.engine.i18n.MessageSource;
import nz.org.riskscape.engine.i18n.MutableMessageSource;
import nz.org.riskscape.wizard.bld.IncrementalBuildState;
import nz.org.riskscape.wizard.bld.InvalidAnswerException;
import nz.org.riskscape.wizard.bld.PipelineChange;
import nz.org.riskscape.wizard.bld.change.NoChange;
import nz.org.riskscape.wizard.survey2.Phase;
import nz.org.riskscape.wizard.survey2.QuestionTree;

/**
 * Survey interface that allows the survey to act as the controller part in an MVC-style set up.  This
 * will allow us to evolve the functionality of surveys without having to make to change so much of the controller logic
 * in the UI each time - all interactions are driven by a) getting the tree of questions based on current state b)
 * answering a question to get the new state.
 */
public interface Survey extends Identified {

  /**
   * Return a string that can be used as a bundle name for looking up a resource bundle / message source for a survey
   * with the given `surveyId`
   */
  static String getSurveyMessageBundleName(String surveyId) {
    return "survey-" + surveyId + "-messages";
  }

  /**
   * A survey that contains no questions and does nothing.  An alternative to null, useful for base cases and as a
   * default
   */
  Survey EMPTY_SURVEY = new EmptySurvey("empty", new MutableMessageSource());


  /**
   * A unique identifier for this survey.
   */
  @Override
  String getId();

  /**
   * A {@link MessageSource} that is specific to this survey.  Should contain i18n text for the various choices and
   * text associated with this survey.
   */
  MessageSource getMessageSource();

  /**
   * A version number that changes to indicate structural changes that might
   * stop a previously saved version from working
   */
  default int getVersion() {
    return 1;
  };

  /**
   * Returns whether or not the replay order of QuestionSets matters, i.e. returns true if QuestionSets
   * must be replayed in the exact same order they are answered by the user. Typically, the Survey
   * implementation itself manages the ordering of QuestionSets, drip-feeding them to the user one at a
   * time. E.g. if the user only ever gets one QuestionSet to choose from, then the replay order doesn't
   * matter. Ordering will matter if there are multiple QuestionSets to choose from that all apply to the same
   * pipeline branch, e.g. replaying A then B will generate a different pipeline to replaying B then A.
   * Note: this is only required because currently the INI parser we use does not preserve the order
   * of the wizard answers at all.
   */
  default boolean isQuestionSetReplayOrdered() {
    return false;
  }

  /**
   * Surveys should make the output of the analysis phase available at a known step name.
   *
   * This is useful to use as the input step to any reports and for the wizard command
   * (in WizardActions) to find the available attributes to list as available once the pipeline only
   * contains capped end steps (e.g results are saved). This occurs when the user wants to
   * list available attributes after they have setup the first output report.
   *
   * @return the name of the analysis output step, or empty if there is none
   */
  default Optional<String> getAnalysisOutputStepName() {
    return Optional.empty();
  }

  /**
   * Returns a {@link PipelineChange} that is created by applying an answer a build state.
   * @param buildState the build state to get a change to.
   * @param answer the answer to apply.  This answer must be for a question that came back from
   * {@link #getNextQuestion(IncrementalBuildState)} using the given build state, or weird things might happen
   * @throws InvalidAnswerException if the answer the user supplied  was invalid
   */
  PipelineChange getPipelineChange(IncrementalBuildState buildState, Answer answer);

  /**
   * Returns a {@link PipelineChange} that is created when skipping the given question.  This might be a no-op or it
   * might be some sort of automated change.
   */
  default PipelineChange skip(IncrementalBuildState buildState, Question toSkip) {
    if (toSkip.isHidden()) {
      return getPipelineChange(buildState, Answer.hidden(toSkip));
    } else if (!toSkip.isRequired()) {
      return new NoChange(Answer.skip(toSkip));
    } else {
      throw new IllegalArgumentException("can not skip question " + toSkip);
    }
  }

  /**
   * @return the set of questions that can be asked next.  The controller code can use this question tree as a source of
   * questions until it is empty - it shouldn't keep asking the survey for the tree between questions.
   */
  QuestionTree getQuestionTree(IncrementalBuildState buildState);

  /**
   * Convenience method that'll return the next available question, or null if there are none to answer.  Uses a null
   * returning method instead of optional to allow a while-getNextQuestion-not-null construct.
   */
  default Question getNextQuestion(IncrementalBuildState buildState) {
    List<Question> questions = getQuestionTree(buildState).getNextQuestions(buildState);
    return questions.size() == 0 ? null : questions.get(0);
  }

  /**
   * @return true if the survey is 'finished enough' to now run something (even if
   *         more optional question-sets remain).
   */
  boolean isFinished(IncrementalBuildState buildState);

  /**
   * @return a list of the currently applicable QuestionSets, i.e. the sets of
   *         questions that are able to be answered next
   */
  List<QuestionSet> getApplicableQuestionSets(IncrementalBuildState buildState);

  /**
   * @return a list of the phases this survey goes through, in sequence order.
   */
  default List<Phase> getDefinedPhases() {
    return Collections.emptyList();
  }

  /**
   * @return a description for this survey, or empty of none is defined
   */
  default Optional<String> getDescription(Locale locale) {
    return Optional.of(getMessageSource().getMessage("survey.description", new Object[0], locale));
  }
}
