import {
  StateNode,
  TLEventHandlers,
  TLShapeId,
  createShapeId,
  IndexKey,
  getIndexAbove,
} from 'tldraw'
import { PolyliniePoint } from '../shapes/shape-types'
import { snapToAngleGrid, Vec2 } from '../utils/angle-snap'

/** Drawing-State: Klick für Klick werden Punkte platziert. Doppelklick beendet. */
export class PolylinieDrawing extends StateNode {
  static override id = 'drawing'

  private shapeId: TLShapeId = '' as TLShapeId
  private points: PolyliniePoint[] = []
  private nextIndex: IndexKey = 'a1' as IndexKey

  override onEnter() {
    const pagePoint = this.editor.inputs.currentPagePoint
    this.shapeId = createShapeId()
    this.nextIndex = 'a1' as IndexKey

    const firstPoint: PolyliniePoint = {
      id: 'p0',
      index: this.nextIndex,
      x: 0,
      y: 0,
    }
    this.nextIndex = getIndexAbove(this.nextIndex)

    // Vorschau-Punkt (wird bei Mausbewegung aktualisiert)
    const previewPoint: PolyliniePoint = {
      id: 'preview',
      index: this.nextIndex,
      x: 0,
      y: 0,
    }
    this.nextIndex = getIndexAbove(this.nextIndex)

    this.points = [firstPoint, previewPoint]

    const pointsRecord: Record<string, PolyliniePoint> = {}
    for (const p of this.points) {
      pointsRecord[p.id] = p
    }

    this.editor.createShape({
      id: this.shapeId,
      type: 'polylinie',
      x: pagePoint.x,
      y: pagePoint.y,
      props: {
        points: pointsRecord,
        label: '',
      },
    })
  }

  override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
    this._updatePreview()
  }

  override onPointerDown: TLEventHandlers['onPointerDown'] = () => {
    // Den Vorschau-Punkt finalisieren
    const preview = this.points[this.points.length - 1]
    if (!preview || preview.id !== 'preview') return

    const snappedPos = this._getSnappedPosition()

    // Vorschau-Punkt wird zum festen Punkt
    const fixedId = `p${this.points.length - 1}`
    const fixedPoint: PolyliniePoint = {
      id: fixedId,
      index: preview.index,
      x: snappedPos.x,
      y: snappedPos.y,
    }

    // Neuen Vorschau-Punkt erstellen
    const newPreview: PolyliniePoint = {
      id: 'preview',
      index: this.nextIndex,
      x: snappedPos.x,
      y: snappedPos.y,
    }
    this.nextIndex = getIndexAbove(this.nextIndex)

    // points-Array aktualisieren: Vorschau ersetzen + neuen Vorschau anhängen
    this.points[this.points.length - 1] = fixedPoint
    this.points.push(newPreview)

    this._syncToShape()
  }

  override onDoubleClick: TLEventHandlers['onDoubleClick'] = () => {
    // Vorschau-Punkt entfernen
    if (this.points.length > 0 && this.points[this.points.length - 1].id === 'preview') {
      this.points.pop()
    }
    // Den letzten durch Doppelklick platzierten Punkt auch entfernen
    // (der erste Klick des Doppelklicks hat bereits einen Punkt gesetzt)
    // Mindestens 2 Punkte behalten
    if (this.points.length < 2) {
      // Zu wenig Punkte → Shape löschen
      this.editor.deleteShape(this.shapeId)
    } else {
      this._syncToShape()
      this._tryConnectToShape()
    }

    this._finish()
  }

  override onCancel: TLEventHandlers['onCancel'] = () => {
    this.editor.deleteShape(this.shapeId)
    this._finish()
  }

  override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
    if (info.key === 'Escape') {
      this.onCancel({} as never)
    }
  }

  /** Aktuelle Mausposition mit Snapping (ab 3. Punkt). */
  private _getSnappedPosition(): Vec2 {
    const pagePoint = this.editor.inputs.currentPagePoint
    const shape = this.editor.getShape(this.shapeId)
    if (!shape) return { x: 0, y: 0 }

    // In lokale Koordinaten umrechnen
    const localX = pagePoint.x - shape.x
    const localY = pagePoint.y - shape.y

    const fixedPoints = this.points.filter((p) => p.id !== 'preview')
    const pointCount = fixedPoints.length

    // Erste 2 Punkte: freie Platzierung
    if (pointCount < 2) {
      return { x: localX, y: localY }
    }

    // Ab 3. Punkt: Winkel-Snapping
    const prev = fixedPoints[pointCount - 1]
    const prevPrev = fixedPoints[pointCount - 2]
    return snapToAngleGrid(prev, prevPrev, { x: localX, y: localY })
  }

  /** Vorschau-Punkt an aktuelle Mausposition aktualisieren. */
  private _updatePreview() {
    const preview = this.points[this.points.length - 1]
    if (!preview || preview.id !== 'preview') return

    const snapped = this._getSnappedPosition()
    preview.x = snapped.x
    preview.y = snapped.y

    this._syncToShape()
  }

  /** Points-Array in den Shape-Store schreiben. */
  private _syncToShape() {
    const pointsRecord: Record<string, PolyliniePoint> = {}
    for (const p of this.points) {
      pointsRecord[p.id] = p
    }

    this.editor.updateShape({
      id: this.shapeId,
      type: 'polylinie',
      props: { points: pointsRecord },
    })
  }

  /** Prüfen ob der Endpunkt auf einem Shape-Rand liegt und ggf. Binding erstellen. */
  private _tryConnectToShape() {
    const shape = this.editor.getShape(this.shapeId)
    if (!shape || this.points.length < 2) return

    const lastPoint = this.points[this.points.length - 1]
    const worldX = shape.x + lastPoint.x
    const worldY = shape.y + lastPoint.y

    // Shapes am Endpunkt finden
    const shapesAtPoint = this.editor.getShapesAtPoint({ x: worldX, y: worldY })
    const connectable = shapesAtPoint.find(
      (s) => ['puffer', 'kreisel', 'foerderer'].includes(s.type) && s.id !== this.shapeId
    )

    if (connectable) {
      // Binding erstellen
      this.editor.createBinding({
        type: 'polylinie-connection',
        fromId: this.shapeId,
        toId: connectable.id,
        props: {
          terminal: 'end',
          normalizedAnchor: { x: worldX, y: worldY },
        },
      })
    }
  }

  /** Zeichnung beenden, zurück zum Idle-State. */
  private _finish() {
    document.querySelector('.tl-container')?.classList.remove('polylinie-active')
    this.points = []
    this.parent.transition('idle')
  }
}
