package eu.mulk.aendggner.aenderung;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Eine Fundstelle im Stammgesetz, als Stapel immer feinerer Komponenten (z.B. „§ 5 Absatz 2 Satz
 * 1“). Verschachtelte Änderungsbefehle konkatenieren ihre Stellen mit {@link #plus}.
 */
public record Stelle(List<Komponente> komponenten) {

  public static final Stelle LEER = new Stelle(List.of());

  public Stelle {
    komponenten = List.copyOf(komponenten);
  }

  public sealed interface Komponente
      permits Paragraph,
          AbsatzNr,
          SatzNr,
          HalbsatzNr,
          NummerNr,
          BuchstabeNr,
          Ueberschrift,
          Inhaltsuebersicht,
          Gliederungseinheit,
          Absatzbezeichnung {}

  /**
   * „§ 5“, „§ 28a“, im bayerischen Landesrecht „Art. 5“ — die Normnummer ohne Sigel, dazu das Sigel
   * selbst („§“ oder „Art.“).
   */
  public record Paragraph(String nummer, String sigel) implements Komponente {
    public Paragraph(String nummer) {
      this(nummer, "§");
    }

    /** Die Einzelnormbezeichnung, wie sie im {@code Gesetz}-Modell steht: „§ 5“, „Art. 5“. */
    public String enbez() {
      return sigel + " " + nummer;
    }
  }

  /** Eine Gliederungseinheit oberhalb des Paragraphen: „Teil 2“, „Abschnitt 3“, „Anlage 8“. */
  public record Gliederungseinheit(String art, String nummer) implements Komponente {
    public String bezeichnung() {
      return nummer.isEmpty() ? art : art + " " + nummer;
    }
  }

  /** „Die Absatzbezeichnung „(2)““ — die reine Absatznummer als Ziel einer Streichung. */
  public record Absatzbezeichnung(String nummer) implements Komponente {}

  /** „Absatz 2“ */
  public record AbsatzNr(String nummer) implements Komponente {}

  /** „Satz 1“ */
  public record SatzNr(String nummer) implements Komponente {}

  /** „Halbsatz 1“ — die Hälfte eines am Semikolon geteilten Satzes (bayerische Zitierweise). */
  public record HalbsatzNr(String nummer) implements Komponente {}

  /** „Nummer 4“ */
  public record NummerNr(String nummer) implements Komponente {}

  /** „Buchstabe c“ (auch Doppelbuchstabe „aa“) */
  public record BuchstabeNr(String kennung) implements Komponente {}

  /** „Die Überschrift“ */
  public record Ueberschrift() implements Komponente {}

  /** „In der Inhaltsübersicht“ */
  public record Inhaltsuebersicht() implements Komponente {}

  public boolean istLeer() {
    return komponenten.isEmpty();
  }

  public Stelle plus(Stelle feiner) {
    if (feiner.istLeer()) {
      return this;
    }
    var neu = new ArrayList<>(komponenten);
    neu.addAll(feiner.komponenten);
    return new Stelle(neu);
  }

  public Optional<Paragraph> paragraph() {
    return komponenten.stream()
        .filter(Paragraph.class::isInstance)
        .map(Paragraph.class::cast)
        .findFirst();
  }

  public Optional<AbsatzNr> absatz() {
    return komponenten.stream()
        .filter(AbsatzNr.class::isInstance)
        .map(AbsatzNr.class::cast)
        .findFirst();
  }

  public boolean betrifftInhaltsuebersicht() {
    return komponenten.stream().anyMatch(Inhaltsuebersicht.class::isInstance);
  }

  public boolean betrifftUeberschrift() {
    return komponenten.stream().anyMatch(Ueberschrift.class::isInstance);
  }

  public List<Gliederungseinheit> gliederungsPfad() {
    return komponenten.stream()
        .filter(Gliederungseinheit.class::isInstance)
        .map(Gliederungseinheit.class::cast)
        .toList();
  }

  public boolean betrifftGliederung() {
    return komponenten.stream().anyMatch(Gliederungseinheit.class::isInstance);
  }

  /**
   * Wahr, wenn die Stelle eine Gliederungseinheit des Überschriften-Gerüsts (Teil, Abschnitt, …)
   * nennt. Anhänge und Anlagen zählen nicht dazu: die sind im gii-XML eigene Normen mit Text und
   * werden wie Paragraphen behandelt (siehe {@link #anlagenEnbez()}).
   */
  public boolean betrifftEchteGliederung() {
    return komponenten.stream()
        .filter(Gliederungseinheit.class::isInstance)
        .map(Gliederungseinheit.class::cast)
        .anyMatch(g -> !istAnlagenArt(g.art()));
  }

  /** Die enbez der adressierten Anhang-/Anlagen-Norm („Anhang“, „Anlage 2“), falls vorhanden. */
  public Optional<String> anlagenEnbez() {
    return komponenten.stream()
        .filter(Gliederungseinheit.class::isInstance)
        .map(Gliederungseinheit.class::cast)
        .filter(g -> istAnlagenArt(g.art()))
        .findFirst()
        .map(Gliederungseinheit::bezeichnung);
  }

  private static boolean istAnlagenArt(String art) {
    return art.equals("Anhang") || art.equals("Anlage");
  }

  public Optional<Absatzbezeichnung> absatzbezeichnung() {
    return komponenten.stream()
        .filter(Absatzbezeichnung.class::isInstance)
        .map(Absatzbezeichnung.class::cast)
        .findFirst();
  }

  public String anzeigeText() {
    if (komponenten.isEmpty()) {
      return "(gesamtes Gesetz)";
    }
    var sb = new StringBuilder();
    for (var komponente : komponenten) {
      if (sb.length() > 0) {
        sb.append(' ');
      }
      sb.append(
          switch (komponente) {
            case Paragraph p -> p.enbez();
            case AbsatzNr a -> "Absatz " + a.nummer();
            case SatzNr s -> "Satz " + s.nummer();
            case HalbsatzNr h -> "Halbsatz " + h.nummer();
            case NummerNr n -> "Nummer " + n.nummer();
            case BuchstabeNr b -> "Buchstabe " + b.kennung();
            case Ueberschrift u -> "Überschrift";
            case Inhaltsuebersicht i -> "Inhaltsübersicht";
            case Gliederungseinheit g -> g.bezeichnung();
            case Absatzbezeichnung a -> "Absatzbezeichnung (" + a.nummer() + ")";
          });
    }
    return sb.toString();
  }
}
