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

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

/**
 * Immutable command builder for riskscape commands
 */
@RequiredArgsConstructor
public class CommandInput {

  public static CommandInput command(String name) {
    return new CommandInput(name, Collections.emptyList(), Optional.empty(), new HashMap<>());
  }

  @RequiredArgsConstructor
  public static final class Option {
    public final String name;
    public final Optional<String> value;

    public String toString() {
      // TODO value escaping?
      String valueString = value.map(v -> String.format("=%s", v)).orElse("");
      return String.format("%s%s", name, valueString);
    }
  }

  public final String command;
  public final List<Option> options;
  public final Optional<CommandInput> prev;
  public final Map<String, String> environmentVariables;

  public CommandInput option(@NonNull String name, Path value) {
    return option(name, value.toAbsolutePath().toString());
  }

  public CommandInput option(@NonNull String name, String value) {
    List<Option> newOptions = new ArrayList<>(this.options.size() + 1);
    newOptions.addAll(this.options);
    newOptions.add(new Option(name, Optional.ofNullable(value)));
    return new CommandInput(this.command, newOptions, this.prev, this.environmentVariables);
  }

  public CommandInput options(@NonNull String name, String... values) {
    List<Option> newOptions = new ArrayList<>(this.options.size() + values.length);
    newOptions.addAll(this.options);

    for (int i = 0; i < values.length; i++) {
      String value = values[i];
      newOptions.add(new Option(name, Optional.ofNullable(value)));
    }

    return new CommandInput(this.command, newOptions, this.prev, this.environmentVariables);
  }

  public CommandInput option(String name) {
    return option(name, (String) null);
  }

  public CommandInput environmentVariable(String name, String value) {
    Map<String, String> newEnvironmentVariables = new HashMap<>(this.environmentVariables);
    newEnvironmentVariables.put(name, value);

    return new CommandInput(this.command, this.options, this.prev, newEnvironmentVariables);
  }

  public CommandInput subcommand(String name) {
    return new CommandInput(name, Collections.emptyList(), Optional.of(this), this.environmentVariables);
  }

  @Override
  public String toString() {
    return String.format("%s %s %s",
        prev.map(ci -> String.format(" %s", ci.toString())).orElse(""),
        command,
        options.stream().map(swch -> swch.toString()).collect(Collectors.joining(" "))
    );
  }

  public Stream<String> toStream() {
    Stream<String> thisOne = Stream.concat(
        Stream.of(command),
        options.stream().map(Option::toString)
    );

    return prev.map(p -> Stream.concat(p.toStream(), thisOne)).orElse(thisOne);
  }

  public CommandInput subcommands(String... subcommands) {
    return subcommands(Arrays.asList(subcommands));
  }

  public CommandInput subcommands(List<String> subcommands) {
    if (subcommands.size() == 0) {
      return this;
    } else {
      String next = subcommands.get(0);
      return subcommand(next).subcommands(subcommands.subList(1, subcommands.size()));
    }
  }

  public CommandInput replaceOption(String name, String value) {
    List<Option> newOptions = new ArrayList<>(this.options.size());

    newOptions.addAll(this.options);
    newOptions.removeIf(opt -> opt.name.equals(name));
    newOptions.add(new Option(name, Optional.of(value)));

    return new CommandInput(this.command, newOptions, this.prev, this.environmentVariables);
  }

}
