import type { GeoPoint, MapSize, MapViewState, ScreenPoint } from "../model/mapTypes";

const TILE_SIZE = 256;
const MAX_LAT = 85.0511;
const DEG = Math.PI / 180;
const RAD = 180 / Math.PI;

export type MapProjection = {
  worldSize: number;
  project: (point: GeoPoint) => ScreenPoint;
  unproject: (point: ScreenPoint) => GeoPoint;
  metersPerPixel: (lat: number) => number;
};

export function createMapProjection(view: MapViewState, size: MapSize): MapProjection {
  const worldSize = TILE_SIZE * 2 ** view.zoom;
  const centerX = lonToWorldX(view.centerLon, worldSize);
  const centerY = latToWorldY(view.centerLat, worldSize);

  return {
    worldSize,
    project(point) {
      let worldX = lonToWorldX(point.lon, worldSize);
      const halfWorld = worldSize / 2;
      while (worldX - centerX > halfWorld) worldX -= worldSize;
      while (worldX - centerX < -halfWorld) worldX += worldSize;

      return {
        x: size.width / 2 + (worldX - centerX),
        y: size.height / 2 + (latToWorldY(point.lat, worldSize) - centerY)
      };
    },
    unproject(point) {
      return {
        lon: normalizeLon(worldXToLon(centerX + (point.x - size.width / 2), worldSize)),
        lat: worldYToLat(centerY + (point.y - size.height / 2), worldSize)
      };
    },
    metersPerPixel(lat) {
      return (Math.cos(lat * DEG) * 2 * Math.PI * 6378137) / worldSize;
    }
  };
}

export function lonToWorldX(lon: number, worldSize: number): number {
  return ((normalizeLon(lon) + 180) / 360) * worldSize;
}

export function latToWorldY(lat: number, worldSize: number): number {
  const clamped = Math.max(-MAX_LAT, Math.min(MAX_LAT, lat));
  const sin = Math.sin(clamped * DEG);
  return (0.5 - Math.log((1 + sin) / (1 - sin)) / (4 * Math.PI)) * worldSize;
}

export function worldXToLon(worldX: number, worldSize: number): number {
  return (worldX / worldSize) * 360 - 180;
}

export function worldYToLat(worldY: number, worldSize: number): number {
  const n = Math.PI - (2 * Math.PI * worldY) / worldSize;
  return RAD * Math.atan(Math.sinh(n));
}

export function normalizeLon(lon: number): number {
  return ((((lon + 180) % 360) + 360) % 360) - 180;
}

