/**
 * PolygonMergeInteraction
 *
 * A four-phase OpenLayers interaction for merging two adjacent polygons:
 *   Phase 1 – SELECT_A:     hover to highlight, click to select polygon A
 *   Phase 2 – SELECT_B:     hover to highlight, click to select polygon B
 *   Phase 3 – CLICK_EDGE_A: hover highlights edge, click to pick shared edge on A
 *   Phase 4 – CLICK_EDGE_B: hover highlights edge, click to pick shared edge on B → merge
 *
 * After a successful merge the two original features are removed and one
 * merged feature (coloured green) is added.  If both originals were parcels,
 * a `mergedparcel` event is fired so external code can present a UPN chooser.
 */

import ol_interaction_Interaction from 'ol/interaction/Interaction';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import Feature from 'ol/Feature';
import { Style, Stroke, Fill, Text } from 'ol/style';
import { LineString, Polygon as PolygonGeom } from 'ol/geom';
import { mergePolygons } from '../geom/polygonMerge.js';
import { showToast } from '../toast.js';

// ── Styles ───────────────────────────────────────────────────────────────────

const HIGHLIGHT_A = new Style({
  stroke: new Stroke({ color: '#0ea5e9', width: 3 }),
  fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),
});

const HIGHLIGHT_B = new Style({
  stroke: new Stroke({ color: '#f59e0b', width: 3 }),
  fill: new Fill({ color: 'rgba(245,158,11,0.15)' }),
});

// Labelled versions for permanent highlights (shown after selection)
const LABEL_A = new Style({
  stroke: new Stroke({ color: '#0ea5e9', width: 3 }),
  fill: new Fill({ color: 'rgba(14,165,233,0.15)' }),
  text: new Text({
    text: 'A',
    font: 'bold 22px Exo, sans-serif',
    fill: new Fill({ color: '#0ea5e9' }),
    stroke: new Stroke({ color: '#fff', width: 4 }),
    overflow: true,
  }),
});

const LABEL_B = new Style({
  stroke: new Stroke({ color: '#f59e0b', width: 3 }),
  fill: new Fill({ color: 'rgba(245,158,11,0.15)' }),
  text: new Text({
    text: 'B',
    font: 'bold 22px Exo, sans-serif',
    fill: new Fill({ color: '#f59e0b' }),
    stroke: new Stroke({ color: '#fff', width: 4 }),
    overflow: true,
  }),
});

const EDGE_STYLE = new Style({
  stroke: new Stroke({ color: '#ec4899', width: 4, lineDash: [10, 6] }),
});

const MERGE_STYLE = new Style({
  stroke: new Stroke({ color: '#10b981', width: 2.5 }),
  fill: new Fill({ color: 'rgba(16,185,129,0.3)' }),
});

// ── Interaction ──────────────────────────────────────────────────────────────

export class PolygonMergeInteraction extends ol_interaction_Interaction {
  /**
   * @param {Object} [options]
   * @param {number} [options.snapDistance=25]  Pixel distance for hover detection.
   * @param {number} [options.tolerance=5]     Map-unit tolerance for shared-edge matching.
   */
  constructor(options = {}) {
    super({
      handleEvent: (e) => this._handleEvent(e),
    });

    this.snapDistance_ = options.snapDistance || 25;
    this.tolerance_ = options.tolerance || 5;

    // Phase: 'select_a' | 'select_b' | 'click_edge_a' | 'click_edge_b'
    this._phase = 'select_a';

    // Selected features and their sources
    this._featureA = null;
    this._sourceA = null;
    this._featureB = null;
    this._sourceB = null;

    // Clicked edge coordinates (map coords)
    this._edgeClickA = null;
    this._edgeClickB = null;

    // Overlay for polygon highlights
    this._highlightSource = new VectorSource({ useSpatialIndex: false });
    this._highlightLayer = new VectorLayer({
      source: this._highlightSource,
      displayInLayerSwitcher: false,
      style: (f) => f.get('_highlightStyle') || HIGHLIGHT_A,
    });

    // Overlay for edge highlights
    this._edgeSource = new VectorSource({ useSpatialIndex: false });
    this._edgeLayer = new VectorLayer({
      source: this._edgeSource,
      displayInLayerSwitcher: false,
      style: EDGE_STYLE,
    });
  }

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

  setMap(map) {
    if (this.getMap()) {
      this.getMap().removeLayer(this._highlightLayer);
      this.getMap().removeLayer(this._edgeLayer);
    }
    super.setMap(map);
    if (map) {
      this._highlightLayer.setMap(map);
      this._edgeLayer.setMap(map);
    }
  }

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

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

  _getSources() {
    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;

    // Escape cancels at any phase
    if (e.type === 'keydown' && e.originalEvent?.key === 'Escape') {
      this._reset();
      return false;
    }

    switch (this._phase) {
      case 'select_a':
        if (e.type === 'pointermove') return this._onSelectMove(e, null);
        if (e.type === 'singleclick') return this._onSelectAClick(e);
        break;
      case 'select_b':
        if (e.type === 'pointermove') return this._onSelectMove(e, this._featureA);
        if (e.type === 'singleclick') return this._onSelectBClick(e);
        break;
      case 'click_edge_a':
        if (e.type === 'pointermove') return this._onEdgeMove(e, this._featureA);
        if (e.type === 'singleclick') return this._onEdgeAClick(e);
        break;
      case 'click_edge_b':
        if (e.type === 'pointermove') return this._onEdgeMove(e, this._featureB);
        if (e.type === 'singleclick') return this._onEdgeBClick(e);
        break;
    }
    return true;
  }

  /* ------------------------------------------------------------------ */
  /*  Phase 1 & 2: SELECT polygons                                       */
  /* ------------------------------------------------------------------ */

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

    // Keep existing highlights for already-selected polygons
    this._highlightSource.clear();
    this._edgeSource.clear();
    this._rebuildHighlights();

    const hit = this._closestPolygon(e, skipFeature);
    if (hit) {
      const style = this._phase === 'select_a' ? HIGHLIGHT_A : HIGHLIGHT_B;
      const clone = hit.feature.clone();
      clone.set('_highlightStyle', style);
      this._highlightSource.addFeature(clone);
      map.getTargetElement().style.cursor = 'pointer';
    } else {
      map.getTargetElement().style.cursor = '';
    }
    return true;
  }

  _onSelectAClick(e) {
    const hit = this._closestPolygon(e, null);
    if (!hit) return true;

    this._featureA = hit.feature;
    this._sourceA = hit.source;
    this._phase = 'select_b';

    this._rebuildHighlights();
    return false;
  }

  _onSelectBClick(e) {
    const hit = this._closestPolygon(e, this._featureA);
    if (!hit) return true;

    this._featureB = hit.feature;
    this._sourceB = hit.source;
    this._phase = 'click_edge_a';

    this._rebuildHighlights();
    this.getMap().getTargetElement().style.cursor = 'crosshair';
    return false;
  }

  /**
   * Find the closest polygon feature within snap distance.
   * Optionally skip a feature (used in phase 2 to avoid re-selecting A).
   */
  _closestPolygon(e, skipFeature) {
    let best = null;
    let bestDist = this.snapDistance_ + 1;

    for (const source of this._getSources()) {
      const feat = source.getClosestFeatureToCoordinate(e.coordinate);
      if (!feat) continue;
      if (skipFeature && feat === skipFeature) 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 3 & 4: CLICK edges                                          */
  /* ------------------------------------------------------------------ */

  _onEdgeMove(e, feature) {
    const map = this.getMap();
    if (!map) return true;

    this._edgeSource.clear();

    const edge = this._closestEdgeSegment(feature, e);
    if (edge) {
      const edgeFeat = new Feature(new LineString([edge.segStart, edge.segEnd]));
      this._edgeSource.addFeature(edgeFeat);
      map.getTargetElement().style.cursor = 'crosshair';
    }
    return true;
  }

  _onEdgeAClick(e) {
    this._edgeClickA = e.coordinate;
    this._phase = 'click_edge_b';
    this._edgeSource.clear();
    return false;
  }

  _onEdgeBClick(e) {
    this._edgeClickB = e.coordinate;
    this._performMerge();
    return false;
  }

  /**
   * Find the closest edge segment of a polygon feature to the cursor.
   */
  _closestEdgeSegment(feature, e) {
    const geom = feature.getGeometry();
    let ring;
    if (geom.getType() === 'Polygon') {
      ring = geom.getCoordinates()[0];
    } else if (geom.getType() === 'MultiPolygon') {
      ring = geom.getCoordinates()[0][0];
    } else {
      return null;
    }

    const resolution = e.frameState.viewState.resolution;
    let bestDist = Infinity;
    let bestSeg = null;
    const n = ring.length - 1;

    for (let i = 0; i < n; i++) {
      const a = ring[i];
      const b = ring[i + 1];
      const dx = b[0] - a[0], dy = b[1] - a[1];
      const lenSq = dx * dx + dy * dy;
      if (lenSq < 1e-20) continue;

      let t = ((e.coordinate[0] - a[0]) * dx + (e.coordinate[1] - a[1]) * dy) / lenSq;
      t = Math.max(0, Math.min(1, t));
      const projX = a[0] + t * dx, projY = a[1] + t * dy;
      const distPx = Math.sqrt((e.coordinate[0] - projX) ** 2 + (e.coordinate[1] - projY) ** 2) / resolution;

      if (distPx < bestDist) {
        bestDist = distPx;
        bestSeg = { segStart: a, segEnd: b };
      }
    }
    return bestDist <= this.snapDistance_ ? bestSeg : null;
  }

  /* ------------------------------------------------------------------ */
  /*  Merge logic                                                        */
  /* ------------------------------------------------------------------ */

  _performMerge() {
    const featureA = this._featureA;
    const featureB = this._featureB;
    const sourceA = this._sourceA;
    const sourceB = this._sourceB;

    // Extract polygon coordinates
    const geomA = featureA.getGeometry();
    const geomB = featureB.getGeometry();
    const coordsA = geomA.getType() === 'Polygon' ? geomA.getCoordinates() : geomA.getCoordinates()[0];
    const coordsB = geomB.getType() === 'Polygon' ? geomB.getCoordinates() : geomB.getCoordinates()[0];

    const result = mergePolygons(coordsA, coordsB, this._edgeClickA, this._edgeClickB, this.tolerance_);

    if (!result.coords) {
      showToast(result.error || 'Merge failed — try clicking on the shared boundary.', 'error', 5000);
      // Return to edge click phase for retry
      this._edgeClickA = null;
      this._edgeClickB = null;
      this._phase = 'click_edge_a';
      this._edgeSource.clear();
      return;
    }

    // Create merged feature (clone A for default properties)
    const mergedFeature = featureA.clone();
    mergedFeature.setGeometry(new PolygonGeom(result.coords));
    mergedFeature.setStyle(MERGE_STYLE);

    // Dispatch beforemerge events
    const evtData = {
      type: 'beforemerge',
      original: [featureA, featureB],
      merged: mergedFeature,
    };
    this.dispatchEvent(evtData);
    sourceA.dispatchEvent({ ...evtData });
    if (sourceB !== sourceA) {
      sourceB.dispatchEvent({ ...evtData });
    }

    // Replace originals with merged
    sourceA.removeFeature(featureA);
    sourceB.removeFeature(featureB);
    sourceA.addFeature(mergedFeature);

    // Dispatch aftermerge events
    const afterEvt = {
      type: 'aftermerge',
      original: [featureA, featureB],
      merged: mergedFeature,
    };
    this.dispatchEvent(afterEvt);
    sourceA.dispatchEvent({ ...afterEvt });
    if (sourceB !== sourceA) {
      sourceB.dispatchEvent({ ...afterEvt });
    }

    // If both features were parcels, fire mergedparcel so MapView can show the UPN chooser
    const isParcelA = featureA.get('_layerType') === 'parcel';
    const isParcelB = featureB.get('_layerType') === 'parcel';
    if (isParcelA && isParcelB) {
      this.dispatchEvent({
        type: 'mergedparcel',
        merged: mergedFeature,
        propsA: featureA.getProperties(),
        propsB: featureB.getProperties(),
        coordinate: this._edgeClickA,
      });
      showToast('Polygons merged — choose which identifier to keep.', 'success');
    } else {
      showToast('Polygons merged successfully.', 'success');
    }

    // Clean up
    this._reset();
  }

  /* ------------------------------------------------------------------ */
  /*  Highlight management                                               */
  /* ------------------------------------------------------------------ */

  /**
   * Rebuild the permanent highlights for already-selected polygons.
   */
  _rebuildHighlights() {
    // Remove previous non-hover highlights
    const toRemove = [];
    this._highlightSource.getFeatures().forEach((f) => {
      if (f.get('_permanent')) toRemove.push(f);
    });
    toRemove.forEach((f) => this._highlightSource.removeFeature(f));

    if (this._featureA) {
      const cloneA = this._featureA.clone();
      cloneA.set('_highlightStyle', LABEL_A);
      cloneA.set('_permanent', true);
      this._highlightSource.addFeature(cloneA);
    }
    if (this._featureB) {
      const cloneB = this._featureB.clone();
      cloneB.set('_highlightStyle', LABEL_B);
      cloneB.set('_permanent', true);
      this._highlightSource.addFeature(cloneB);
    }
  }

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

  _reset() {
    this._phase = 'select_a';
    this._featureA = null;
    this._sourceA = null;
    this._featureB = null;
    this._sourceB = null;
    this._edgeClickA = null;
    this._edgeClickB = null;
    this._highlightSource.clear();
    this._edgeSource.clear();

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