// Schnittlinie — setzt die Grundriss-Schnitt-/Ansichtslinie einer Zeichnungsebene
// (DrawingLevel, kind "section"/"elevation") per zwei Klicks (Start → Ende). Die
// Linie steuert `sectionPlaneFromLevel` (plan/toSection.ts): senkrecht auf ihr
// steht die Blickrichtung (directionSign, Voreinstellung +1).
//
// Schritte:
//   1) „Schnitt-Startpunkt:" → Punkt
//   2) „Schnitt-Endpunkt:"   → Live-Linie + Richtungspfeil; Klick committet.
//
// Ziel-Ebene (im Commit bestimmt): existiert GENAU EINE Ebene der gewählten Art
// OHNE gesetzte Linie (frisch angelegter Platzhalter), wird deren Linie gesetzt;
// sonst wird eine NEUE Ebene angelegt (Name „Schnitt B/C…" bzw. „Ansicht …").
//
// Die Art (Schnitt/Ansicht) ist als Inline-Option umschaltbar — dieselbe Mechanik
// für beide, nur `kind` unterscheidet sich (Alias `viewline` startet mit
// „elevation"). Bezeichner englisch, sichtbarer Text via t().

import { t } from "../../i18n";
import type { DrawingLevel, DrawingLevelKind } from "../../model/types";
import { leftNormal, normalize, sub } from "../../model/geometry";
import type {
  Command,
  CommandResult,
  CommandState,
  CmdOption,
  DraftShape,
  Project,
  ToolDraft,
  Vec2,
} from "../types";

/** Nur Schnitt/Ansicht tragen eine Linie. */
type LineKind = "section" | "elevation";

/** Wählbare Art (Inline-Optionen der Befehlszeile). */
const KIND_OPTIONS: CmdOption[] = [
  { id: "section", labelKey: "cmd.sectionline.kind.section" },
  { id: "elevation", labelKey: "cmd.sectionline.kind.elevation" },
];
const KIND_IDS = new Set(KIND_OPTIONS.map((o) => o.id));

/**
 * Richtungspfeil-Vorschau: kurze Linie vom Mittelpunkt der Schnittlinie entlang
 * der Blicknormalen (leftNormal der Linie, per directionSign gedreht) plus zwei
 * Flügel-Segmente — zeigt, wohin der Schnitt „blickt".
 */
function directionArrow(a: Vec2, b: Vec2, sign: 1 | -1): DraftShape[] {
  const dir = sub(b, a);
  const len = Math.hypot(dir.x, dir.y);
  if (len < 1e-6) return [];
  const u = normalize(dir);
  const n = leftNormal(u); // (-uy, ux)
  const nx = n.x * sign;
  const ny = n.y * sign;
  const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
  // Pfeillänge ~ min(1 m, Viertel der Linienlänge), damit die Vorschau lesbar bleibt.
  const arrowLen = Math.min(1, len / 4);
  const tip = { x: mid.x + nx * arrowLen, y: mid.y + ny * arrowLen };
  const wing = arrowLen * 0.35;
  // Flügel: vom Spitzenpunkt zurück, je halb entlang −u und +u geneigt.
  const back = { x: tip.x - nx * wing * 2, y: tip.y - ny * wing * 2 };
  const w1 = { x: back.x + u.x * wing, y: back.y + u.y * wing };
  const w2 = { x: back.x - u.x * wing, y: back.y - u.y * wing };
  return [
    { kind: "line", a: mid, b: tip },
    { kind: "line", a: tip, b: w1 },
    { kind: "line", a: tip, b: w2 },
  ];
}

/** Vorschau: Schnittlinie + Richtungspfeil + Länge im HUD. */
function lineDraft(a: Vec2, cursor: Vec2 | null, sign: 1 | -1): ToolDraft {
  if (!cursor) return { preview: [{ kind: "poly", pts: [a] }], vertices: [a] };
  const preview: DraftShape[] = [
    { kind: "line", a, b: cursor },
    ...directionArrow(a, cursor, sign),
  ];
  const draft: ToolDraft = { preview, vertices: [a] };
  draft.hud = {
    at: cursor,
    text: `${Math.hypot(cursor.x - a.x, cursor.y - a.y).toFixed(2)} m`,
  };
  return draft;
}

/** Nächster freier Ebenen-Name der Art (A, B, C … über die Anzahl bestehender). */
function nextLevelName(p: Project, kind: LineKind): string {
  const count = p.drawingLevels.filter((z) => z.kind === kind).length;
  // Buchstaben-Suffix (A, B, …, Z, dann AA…) — klassische Schnitt-Benennung.
  let n = count;
  let letters = "";
  do {
    letters = String.fromCharCode(65 + (n % 26)) + letters;
    n = Math.floor(n / 26) - 1;
  } while (n >= 0);
  const key = kind === "section" ? "cmd.sectionline.newSection" : "cmd.sectionline.newElevation";
  return t(key, { letter: letters });
}

/**
 * Setzt die Schnittlinie [a, b] auf die Ziel-Ebene (immutabel). Existiert GENAU
 * EINE Ebene der Art ohne Linie, wird sie belegt; sonst wird eine neue angelegt.
 * Entartete (zu kurze) Linien werden verworfen.
 */
function commitLine(p: Project, a: Vec2, b: Vec2, kind: LineKind): Project {
  if (Math.hypot(b.x - a.x, b.y - a.y) < 0.1) return p;
  const line: [Vec2, Vec2] = [a, b];
  const orphans = p.drawingLevels.filter((z) => z.kind === kind && !z.linePoints);
  if (orphans.length === 1) {
    const targetId = orphans[0].id;
    return {
      ...p,
      drawingLevels: p.drawingLevels.map((z) =>
        z.id === targetId ? { ...z, linePoints: line, directionSign: z.directionSign ?? 1 } : z,
      ),
    };
  }
  const level: DrawingLevel = {
    id: `${kind}-${Date.now()}`,
    name: nextLevelName(p, kind),
    kind: kind as DrawingLevelKind,
    visible: true,
    locked: false,
    linePoints: line,
    directionSign: 1,
  };
  return { ...p, drawingLevels: [...p.drawingLevels, level] };
}

interface LineIdle extends CommandState {
  phase: "start";
  kind: LineKind;
}
interface LineEnd extends CommandState {
  phase: "end";
  a: Vec2;
  cursor: Vec2 | null;
  kind: LineKind;
}
type LineState = LineIdle | LineEnd;

const DIRECTION_SIGN: 1 = 1;

function makeCommand(name: string, startKind: LineKind): Command {
  const idle = (kind: LineKind): [CommandState, CommandResult] => [
    { phase: "start", kind, lastPoint: null } as LineIdle,
    { draft: null, done: true },
  ];
  return {
    name,
    labelKey: "cmd.sectionline.label",
    // Nicht floorOnly: eine Schnittlinie zieht man IM Grundriss, wo Bauteile
    // liegen — die aktive Ebene ist dann ein Geschoss. Aber der Befehl soll auch
    // auf freien Zeichnungsebenen nutzbar bleiben; die Prüfung übernimmt der
    // Grundriss-Kontext (keine harte Sperre nötig).
    floorOnly: true,
    prompt: (s) => ((s as LineState).phase === "end" ? "cmd.sectionline.end" : "cmd.sectionline.start"),
    accepts: () => ["point", "option"],
    options: () => KIND_OPTIONS,
    init: (): LineIdle => ({ phase: "start", kind: startKind, lastPoint: null }),

    onInput: (state, input): [CommandState, CommandResult] => {
      const s = state as LineState;
      if (input.kind === "option" && KIND_IDS.has(input.id)) {
        const ns = { ...s, kind: input.id as LineKind } as LineState;
        const draft =
          ns.phase === "end" ? lineDraft(ns.a, ns.cursor, DIRECTION_SIGN) : null;
        return [ns, { draft }];
      }
      if (input.kind !== "point") {
        return [
          s,
          { draft: s.phase === "end" ? lineDraft(s.a, s.cursor, DIRECTION_SIGN) : null },
        ];
      }
      if (s.phase !== "end") {
        const ns: LineEnd = {
          phase: "end",
          a: input.point,
          cursor: input.point,
          kind: s.kind,
          lastPoint: input.point,
        };
        return [ns, { draft: lineDraft(input.point, input.point, DIRECTION_SIGN) }];
      }
      const a = s.a;
      const b = input.point;
      const kind = s.kind;
      return [
        { phase: "start", kind, lastPoint: null } as LineIdle,
        { draft: null, done: true, commit: (p) => commitLine(p, a, b, kind) },
      ];
    },

    onMove: (state, point): [CommandState, CommandResult] => {
      const s = state as LineState;
      if (s.phase !== "end") return [s, { draft: null }];
      const ns: LineEnd = { ...s, cursor: point };
      return [ns, { draft: lineDraft(s.a, point, DIRECTION_SIGN) }];
    },

    onConfirm: (state): [CommandState, CommandResult] => idle((state as LineState).kind),
    onCancel: (state): [CommandState, CommandResult] => idle((state as LineState).kind),
  };
}

/** Schnittlinie (startet als „section"). */
export const sectionLineCommand: Command = makeCommand("sectionline", "section");
/** Ansichtslinie (startet als „elevation"; identische Mechanik). */
export const viewLineCommand: Command = makeCommand("viewline", "elevation");

// Für Tests: die reinen Datenpfad-Helfer.
export const _test = { commitLine, nextLevelName, directionArrow };
