/**
 * PolygonSplitInteraction
 *
 * A two-phase OpenLayers interaction for splitting polygons:
 *   Phase 1 – SELECT: hover to highlight, click to select a polygon
 *   Phase 2 – DRAW:   draw a cutting line, double-click to finish
 *
 * After a successful split the original feature is removed and two new
 * coloured features are added.  The interaction fires `beforesplit` and
 * `aftersplit` events compatible with ol-ext's UndoRedo.
 */

import ol_interaction_Interaction from 'ol/interaction/Interaction';
import ol_interaction_Draw from 'ol/interaction/Draw';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Feature from 'ol/Feature';
import { Style, Stroke, Fill, Circle as CircleStyle } from 'ol/style';
import { LineString } from 'ol/geom';
import { Polygon as PolygonGeom } from 'ol/geom';
import { splitPolygonByLine } from '../geom/polygonSplit.js';
import { showToast } from '../toast.js';

// Marker colours for the two split pieces
const SPLIT_COLORS = [
  { stroke: '#ef4444', fill: 'rgba(239,68,68,0.25)' },   // red
  { stroke: '#3b82f6', fill: 'rgba(59,130,246,0.25)' },   // blue
];

// Highlight style for the selected polygon (phase 1)
const HIGHLIGHT_STYLE = new Style({
  stroke: new Stroke({ color: '#0ea5e9', width: 3 }),
  fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),
});

// Style for the cutting-line sketch (phase 2)
const SKETCH_STYLE = new Style({
  stroke: new Stroke({ color: '#f43f5e', width: 2, lineDash: [8, 6] }),
  image: new CircleStyle({
    radius: 5,
    fill: new Fill({ color: '#f43f5e' }),
    stroke: new Stroke({ color: '#fff', width: 1.5 }),
  }),
});

export class PolygonSplitInteraction extends ol_interaction_Interaction {
  /**
   * @param {Object} options
   * @param {VectorSource|VectorSource[]} [options.sources]  Sources containing
   *   polygons to split.  If omitted the interaction searches all visible
   *   vector layers on the map.
   * @param {number} [options.snapDistance=25]  Pixel distance for hover highlight.
   */
  constructor(options = {}) {
    super({
      handleEvent: (e) => this._handleEvent(e),
    });

    this.snapDistance_ = options.snapDistance || 25;
    this._sources = options.sources
      ? (Array.isArray(options.sources) ? options.sources : [options.sources])
      : null;

    // Phase: 'select' | 'draw' | 'pick'
    this._phase = 'select';
    this._selectedFeature = null;
    this._selectedSource = null;
    this._drawInteraction = null;
    this._splitFeatures = null; // the two pieces (for pick phase)

    // Overlay layer for highlighting the polygon under the cursor / selected
    this._overlaySource = new VectorSource({ useSpatialIndex: false });
    this._overlayLayer = new VectorLayer({
      source: this._overlaySource,
      displayInLayerSwitcher: false,
      style: HIGHLIGHT_STYLE,
    });
  }

  /* ------------------------------------------------------------------ */
  /*  Map lifecycle                                                      */
  /* ------------------------------------------------------------------ */

  setMap(map) {
    if (this.getMap()) {
      this.getMap().removeLayer(this._overlayLayer);
      this._removeDrawInteraction();
    }
    super.setMap(map);
    if (map) {
      this._overlayLayer.setMap(map);
    }
  }

  setActive(active) {
    super.setActive(active);
    if (!active) {
      this._reset();
    }
  }

  /* ------------------------------------------------------------------ */
  /*  Source helpers                                                      */
  /* ------------------------------------------------------------------ */

  _getSources() {
    if (this._sources) return this._sources;
    if (!this.getMap()) return [];
    const sources = [];
    const collect = (layers) => {
      layers.forEach((layer) => {
        if (layer.getVisible()) {
          if (layer.getSource && layer.getSource() instanceof VectorSource) {
            sources.push(layer.getSource());
          } else if (layer.getLayers) {
            collect(layer.getLayers());
          }
        }
      });
    };
    collect(this.getMap().getLayers());
    return sources;
  }

  /* ------------------------------------------------------------------ */
  /*  Event router                                                       */
  /* ------------------------------------------------------------------ */

  _handleEvent(e) {
    if (!this.getActive()) return true;

    if (this._phase === 'select') {
      if (e.type === 'pointermove') return this._onSelectMove(e);
      if (e.type === 'singleclick') return this._onSelectClick(e);
    }
    // In 'draw' phase the Draw interaction handles events directly;
    // we only intercept Escape to cancel.
    if (this._phase === 'draw') {
      if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {
        this._cancelDraw();
        return false;
      }
    }

    // In 'pick' phase the user selects which split piece keeps the UPN
    if (this._phase === 'pick') {
      if (e.type === 'pointermove') return this._onPickMove(e);
      if (e.type === 'singleclick') return this._onPickClick(e);
      if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {
        this._reset();
        return false;
      }
    }

    return true;
  }

  /* ------------------------------------------------------------------ */
  /*  Phase 1: SELECT                                                    */
  /* ------------------------------------------------------------------ */

  _onSelectMove(e) {
    const map = this.getMap();
    if (!map) return true;

    this._overlaySource.clear();

    const hit = this._closestPolygon(e);
    if (hit) {
      // Show highlight copy
      const clone = hit.feature.clone();
      this._overlaySource.addFeature(clone);
      map.getTargetElement().style.cursor = 'pointer';
    } else {
      map.getTargetElement().style.cursor = '';
    }
    return true;
  }

  _onSelectClick(e) {
    const hit = this._closestPolygon(e);
    if (!hit) return true;

    this._selectedFeature = hit.feature;
    this._selectedSource = hit.source;

    // Keep highlight visible during draw phase
    this._overlaySource.clear();
    const clone = hit.feature.clone();
    this._overlaySource.addFeature(clone);

    this._startDrawPhase();
    return false; // consume the click
  }

  /**
   * Find the closest polygon feature within snap distance.
   */
  _closestPolygon(e) {
    let best = null;
    let bestDist = this.snapDistance_ + 1;

    for (const source of this._getSources()) {
      const feat = source.getClosestFeatureToCoordinate(e.coordinate);
      if (!feat) continue;
      const geom = feat.getGeometry();
      if (!geom) continue;
      const type = geom.getType();
      if (type !== 'Polygon' && type !== 'MultiPolygon') continue;

      const closest = geom.getClosestPoint(e.coordinate);
      const line = new LineString([e.coordinate, closest]);
      const distPx = line.getLength() / e.frameState.viewState.resolution;

      if (distPx < bestDist) {
        bestDist = distPx;
        best = { feature: feat, source, coord: closest };
      }
    }
    return best;
  }

  /* ------------------------------------------------------------------ */
  /*  Phase 2: DRAW cutting line                                         */
  /* ------------------------------------------------------------------ */

  _startDrawPhase() {
    this._phase = 'draw';
    const map = this.getMap();
    if (!map) return;

    map.getTargetElement().style.cursor = 'crosshair';

    this._drawInteraction = new ol_interaction_Draw({
      type: 'LineString',
      style: SKETCH_STYLE,
    });

    this._drawInteraction.on('drawend', (evt) => {
      const cuttingLine = evt.feature.getGeometry().getCoordinates();
      this._performSplit(cuttingLine);
    });

    map.addInteraction(this._drawInteraction);
  }

  _removeDrawInteraction() {
    if (this._drawInteraction && this.getMap()) {
      this.getMap().removeInteraction(this._drawInteraction);
    }
    this._drawInteraction = null;
  }

  _cancelDraw() {
    this._removeDrawInteraction();
    this._reset();
  }

  /* ------------------------------------------------------------------ */
  /*  Split logic                                                        */
  /* ------------------------------------------------------------------ */

  _performSplit(cuttingLineCoords) {
    const feature = this._selectedFeature;
    const source = this._selectedSource;
    const geom = feature.getGeometry();

    let polygonCoords;
    if (geom.getType() === 'Polygon') {
      polygonCoords = geom.getCoordinates();
    } else if (geom.getType() === 'MultiPolygon') {
      // For MultiPolygon, try to split each sub-polygon and use the
      // first one that produces a valid result.
      // For now, use the first polygon ring.
      polygonCoords = geom.getCoordinates()[0];
    }

    const result = splitPolygonByLine(polygonCoords, cuttingLineCoords);

    if (!result) {
      console.warn('[PolygonSplit] Split failed — line must cross the polygon boundary at exactly 2 points.');
      // Stay in draw phase so user can retry
      this._removeDrawInteraction();
      this._startDrawPhase();
      return;
    }

    const [coordsA, coordsB] = result;

    // Create two new features from the split result
    const featureA = feature.clone();
    featureA.setGeometry(new PolygonGeom(coordsA));
    featureA.setStyle(new Style({
      stroke: new Stroke({ color: SPLIT_COLORS[0].stroke, width: 2.5 }),
      fill: new Fill({ color: SPLIT_COLORS[0].fill }),
    }));

    const featureB = feature.clone();
    featureB.setGeometry(new PolygonGeom(coordsB));
    featureB.setStyle(new Style({
      stroke: new Stroke({ color: SPLIT_COLORS[1].stroke, width: 2.5 }),
      fill: new Fill({ color: SPLIT_COLORS[1].fill }),
    }));

    // Dispatch beforesplit (compatible with ol-ext UndoRedo)
    const splitFeatures = [featureA, featureB];
    this.dispatchEvent({
      type: 'beforesplit',
      original: feature,
      features: splitFeatures,
    });
    source.dispatchEvent({
      type: 'beforesplit',
      original: feature,
      features: splitFeatures,
    });

    // Replace the original feature
    source.removeFeature(feature);
    source.addFeature(featureA);
    source.addFeature(featureB);

    // Dispatch aftersplit
    this.dispatchEvent({
      type: 'aftersplit',
      original: feature,
      features: splitFeatures,
    });
    source.dispatchEvent({
      type: 'aftersplit',
      original: feature,
      features: splitFeatures,
    });

    // Clean up draw interaction
    this._removeDrawInteraction();

    // If the original was a parcel, enter pick phase for UPN assignment
    const isParcel = feature.get('_layerType') === 'parcel';
    if (isParcel) {
      this._splitFeatures = splitFeatures;
      this._phase = 'pick';
      this._overlaySource.clear();
      const map = this.getMap();
      if (map) map.getTargetElement().style.cursor = '';
      showToast('Click the polygon that should keep the original identifier.', 'info', 5000);

      this.dispatchEvent({
        type: 'splitparcel',
        features: splitFeatures,
        originalProps: feature.getProperties(),
        source,
      });
    } else {
      this._reset();
    }
  }

  /* ------------------------------------------------------------------ */
  /*  Phase 3: PICK — select which split piece keeps the UPN             */
  /* ------------------------------------------------------------------ */

  _onPickMove(e) {
    const map = this.getMap();
    if (!map) return true;

    this._overlaySource.clear();

    const hit = this._closestSplitPiece(e);
    if (hit) {
      const clone = hit.clone();
      this._overlaySource.addFeature(clone);
      map.getTargetElement().style.cursor = 'pointer';
    } else {
      map.getTargetElement().style.cursor = '';
    }
    return true;
  }

  _onPickClick(e) {
    const hit = this._closestSplitPiece(e);
    if (!hit) return true;

    this.dispatchEvent({
      type: 'splitpick',
      picked: hit,
      features: this._splitFeatures,
    });

    this._reset();
    return false;
  }

  /**
   * Find the closest split piece to the cursor.
   */
  _closestSplitPiece(e) {
    if (!this._splitFeatures) return null;
    let best = null;
    let bestDist = this.snapDistance_ + 1;

    for (const feat of this._splitFeatures) {
      const geom = feat.getGeometry();
      if (!geom) continue;
      const closest = geom.getClosestPoint(e.coordinate);
      const line = new LineString([e.coordinate, closest]);
      const distPx = line.getLength() / e.frameState.viewState.resolution;
      if (distPx < bestDist) {
        bestDist = distPx;
        best = feat;
      }
    }
    return best;
  }

  /* ------------------------------------------------------------------ */
  /*  Reset                                                              */
  /* ------------------------------------------------------------------ */

  _reset() {
    this._phase = 'select';
    this._selectedFeature = null;
    this._selectedSource = null;
    this._splitFeatures = null;
    this._overlaySource.clear();
    this._removeDrawInteraction();

    const map = this.getMap();
    if (map) {
      map.getTargetElement().style.cursor = '';
    }
  }
}
