import type { LiveShipFeature, ViewportBounds } from '../model/liveShip.types';

interface RenderConfig {
  defaultMinInterval: number;
  maxInterval: number;
  targetRenderTime: number;
  maxRenderTime: number;
}

interface DensityConfig {
  maxZoom: number;
  maxPerCell: number;
  gridSizeMultiplier: number;
}

type RenderCallback = (ships: LiveShipFeature[], trigger: number) => void;

const RENDER_CONFIG: RenderConfig = {
  defaultMinInterval: 1000,
  maxInterval: 5000,
  targetRenderTime: 100,
  maxRenderTime: 500,
};

const ZOOM_MIN_INTERVAL: Record<number, number> = {
  7: 4000,
  8: 3500,
  9: 3000,
  10: 2500,
  11: 2000,
  12: 1500,
  13: 1500,
  14: 1000,
};

const DENSITY_LIMITS: DensityConfig[] = [
  { maxZoom: 5, maxPerCell: 20, gridSizeMultiplier: 120 },
  { maxZoom: 6, maxPerCell: 25, gridSizeMultiplier: 100 },
  { maxZoom: 7, maxPerCell: 33, gridSizeMultiplier: 80 },
  { maxZoom: 8, maxPerCell: 35, gridSizeMultiplier: 75 },
  { maxZoom: 9, maxPerCell: 38, gridSizeMultiplier: 70 },
  { maxZoom: 10, maxPerCell: 40, gridSizeMultiplier: 55 },
  { maxZoom: 11, maxPerCell: 43, gridSizeMultiplier: 40 },
  { maxZoom: Number.POSITIVE_INFINITY, maxPerCell: Number.POSITIVE_INFINITY, gridSizeMultiplier: 30 },
];

const SHIP_KIND_PRIORITY: Record<string, number> = {
  '000021': 2,
  '000025': 3,
  '000022': 4,
  '000024': 6,
  '000023': 7,
  '000020': 8,
  '000027': 9,
  '000028': 10,
};

function getShipPriority(ship: LiveShipFeature): number {
  return SHIP_KIND_PRIORITY[ship.signalKindCode] ?? 11;
}

function getDensityConfig(zoomLevel: number): DensityConfig {
  for (const config of DENSITY_LIMITS) {
    if (zoomLevel <= config.maxZoom) return config;
  }
  return DENSITY_LIMITS[DENSITY_LIMITS.length - 1];
}

function getMinIntervalByZoom(zoom: number): number {
  const zoomInt = Math.floor(zoom);
  if (zoomInt <= 7) return ZOOM_MIN_INTERVAL[7];
  if (zoomInt >= 14) return ZOOM_MIN_INTERVAL[14];
  return ZOOM_MIN_INTERVAL[zoomInt] || RENDER_CONFIG.defaultMinInterval;
}

function isInViewport(ship: LiveShipFeature, bounds: ViewportBounds): boolean {
  if (ship.latitude < bounds.minLat || ship.latitude > bounds.maxLat) return false;
  if (bounds.minLon <= bounds.maxLon) {
    return ship.longitude >= bounds.minLon && ship.longitude <= bounds.maxLon;
  }
  return ship.longitude >= bounds.minLon || ship.longitude <= bounds.maxLon;
}

function applyDensityLimit(ships: LiveShipFeature[], zoomLevel: number): LiveShipFeature[] {
  const config = getDensityConfig(zoomLevel);
  if (config.maxPerCell === Number.POSITIVE_INFINITY) return ships;

  const sorted = [...ships].sort((a, b) => getShipPriority(a) - getShipPriority(b));
  const gridSize = Math.pow(2, -zoomLevel) * config.gridSizeMultiplier;
  const gridCounts = new Map<string, number>();
  const result: LiveShipFeature[] = [];

  for (const ship of sorted) {
    const gridX = Math.floor(ship.longitude / gridSize);
    const gridY = Math.floor(ship.latitude / gridSize);
    const key = `${gridX},${gridY}`;
    const count = gridCounts.get(key) || 0;
    if (count < config.maxPerCell) {
      result.push(ship);
      gridCounts.set(key, count + 1);
    }
  }

  return result.reverse();
}

class ShipBatchRenderer {
  private data: LiveShipFeature[] = [];
  private callback: RenderCallback | null = null;
  private viewportBounds: ViewportBounds | null = null;
  private currentZoom = 10;
  private pendingRender = false;
  private isRendering = false;
  private animationHandle: ReturnType<typeof setTimeout> | number | null = null;
  private lastRenderTime = 0;
  private currentInterval = RENDER_CONFIG.defaultMinInterval;
  private renderTrigger = 0;
  private lastRenderedShips: LiveShipFeature[] = [];

  initialize(callback: RenderCallback): void {
    this.callback = callback;
  }

  setData(data: LiveShipFeature[]): void {
    this.data = data;
    this.requestRender();
  }

  setViewportBounds(bounds: ViewportBounds | null): void {
    this.viewportBounds = bounds;
  }

  setZoom(zoom: number): boolean {
    const prevInt = Math.floor(this.currentZoom);
    const nextInt = Math.floor(zoom);
    this.currentZoom = zoom;

    const nextMin = getMinIntervalByZoom(zoom);
    if (nextMin > this.currentInterval) {
      this.currentInterval = nextMin;
    }

    return prevInt !== nextInt;
  }

  requestRender(): void {
    this.pendingRender = true;
    if (this.animationHandle != null) return;
    this.scheduleRender();
  }

  immediateRender(): void {
    if (this.animationHandle != null) {
      clearTimeout(this.animationHandle as ReturnType<typeof setTimeout>);
      cancelAnimationFrame(this.animationHandle as number);
      this.animationHandle = null;
    }
    this.pendingRender = false;
    requestAnimationFrame(() => this.executeRender());
  }

  getRenderedShips(): LiveShipFeature[] {
    return this.lastRenderedShips;
  }

  dispose(): void {
    if (this.animationHandle != null) {
      clearTimeout(this.animationHandle as ReturnType<typeof setTimeout>);
      cancelAnimationFrame(this.animationHandle as number);
      this.animationHandle = null;
    }
    this.pendingRender = false;
    this.callback = null;
    this.lastRenderedShips = [];
  }

  private scheduleRender(): void {
    const elapsed = Date.now() - this.lastRenderTime;
    const delay = Math.max(0, this.currentInterval - elapsed);

    this.animationHandle = setTimeout(() => {
      this.animationHandle = requestAnimationFrame(() => this.executeRender());
    }, delay);
  }

  private executeRender(): void {
    if (this.isRendering) {
      this.animationHandle = null;
      return;
    }

    if (!this.callback) {
      this.animationHandle = null;
      return;
    }

    const startTime = performance.now();
    this.isRendering = true;

    try {
      let ships = this.data;
      if (this.viewportBounds) {
        ships = ships.filter((ship) => isInViewport(ship, this.viewportBounds as ViewportBounds));
      }

      const densityLimited = applyDensityLimit(ships, this.currentZoom);
      this.renderTrigger += 1;
      this.lastRenderedShips = densityLimited;
      this.callback(densityLimited, this.renderTrigger);

      const renderTime = performance.now() - startTime;
      this.adjustRenderInterval(renderTime);
    } finally {
      this.isRendering = false;
      this.lastRenderTime = Date.now();
      this.animationHandle = null;
      if (this.pendingRender) {
        this.pendingRender = false;
        this.scheduleRender();
      }
    }
  }

  private adjustRenderInterval(renderTime: number): void {
    const minInterval = getMinIntervalByZoom(this.currentZoom);

    if (renderTime > RENDER_CONFIG.maxRenderTime) {
      this.currentInterval = Math.min(this.currentInterval * 1.2, RENDER_CONFIG.maxInterval);
      return;
    }

    if (renderTime < RENDER_CONFIG.targetRenderTime) {
      this.currentInterval = Math.max(this.currentInterval * 0.9, minInterval);
    }
  }
}

export { applyDensityLimit, getDensityConfig, getMinIntervalByZoom };
export type { RenderCallback };
export default ShipBatchRenderer;
