import { getAMapConfig, isAMapConfigured } from '../config/appConfig';

export type AMapPlugin = 'AMap.Scale' | 'AMap.Geocoder' | 'AMap.ToolBar';

export type AMapMap = {
  add: (overlay: AMapOverlay | AMapOverlay[]) => void;
  addControl: (control: unknown) => void;
  setFitView: (overlays?: AMapOverlay[] | null, immediate?: boolean, padding?: number[]) => void;
  destroy: () => void;
  on?: (eventName: string, handler: (event: unknown) => void) => void;
  off?: (eventName: string, handler: (event: unknown) => void) => void;
  getZoom?: () => number;
  getBounds?: () => {
    getSouthWest?: () => { getLng?: () => number; getLat?: () => number };
    getNorthEast?: () => { getLng?: () => number; getLat?: () => number };
  };
  setCenter?: (center: [number, number]) => void;
  setZoomAndCenter?: (zoom: number, center: [number, number]) => void;
  panTo?: (center: [number, number], duration?: number) => void;
  resize?: () => void;
};

export type AMapOverlay = {
  setMap?: (map: AMapMap | null) => void;
  on?: (eventName: string, handler: () => void) => void;
  off?: (eventName: string, handler: () => void) => void;
  setPosition?: (position: [number, number]) => void;
  setPath?: (path: [number, number][]) => void;
};

export type AMapMassMarks = {
  setMap: (map: AMapMap | null) => void;
  setData: (data: AMapMassPoint[]) => void;
  setStyle?: (style: Array<{ url: string; anchor: unknown; size: unknown }>) => void;
  on: (eventName: string, handler: (event: { data: AMapMassPoint }) => void) => void;
  off?: (eventName: string, handler: (event: { data: AMapMassPoint }) => void) => void;
};

export type AMapLabelsLayer = {
  setMap: (map: AMapMap | null) => void;
  add: (markers: AMapOverlay | AMapOverlay[]) => void;
  remove: (markers: AMapOverlay | AMapOverlay[]) => void;
  clear: () => void;
};

export type AMapMassPoint = {
  lnglat: [number, number];
  style: number;
  id: string;
  label: string;
  sourceToken?: string;
};

type AMapAddressComponent = {
  province?: string;
  city?: string | string[];
  district?: string;
  township?: string;
  adcode?: string;
};

type AMapGeocoderResult = {
  info?: string;
  regeocode?: {
    formattedAddress?: string;
    addressComponent?: AMapAddressComponent;
  };
};

export type AMapAddress = {
  provider: 'AMap Web JS';
  formattedAddress: string;
  province?: string;
  city?: string;
  district?: string;
  township?: string;
  adcode?: string;
};

export type AMapLike = {
  Map: new (container: HTMLDivElement, options: Record<string, unknown>) => AMapMap;
  Marker: new (options: Record<string, unknown>) => AMapOverlay;
  Polyline: new (options: Record<string, unknown>) => AMapOverlay;
  Scale: new () => unknown;
  ToolBar?: new (options?: Record<string, unknown>) => unknown;
  Size: new (width: number, height: number) => unknown;
  Pixel: new (x: number, y: number) => unknown;
  MassMarks: new (data: AMapMassPoint[], options: Record<string, unknown>) => AMapMassMarks;
  LabelsLayer?: new (options?: Record<string, unknown>) => AMapLabelsLayer;
  LabelMarker?: new (options: Record<string, unknown>) => AMapOverlay;
  Geocoder?: new (options?: Record<string, unknown>) => {
    getAddress: (
      lnglat: [number, number],
      callback: (status: string, result: AMapGeocoderResult | string) => void
    ) => void;
  };
};

const amapLoaderPromises = new Map<string, Promise<AMapLike>>();

function loadScript(src: string) {
  return new Promise<void>((resolve, reject) => {
    if (window.AMapLoader) {
      resolve();
      return;
    }
    const existing = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`);
    if (existing) {
      if (existing.dataset.loadState === 'loaded') {
        existing.remove();
        loadScript(src).then(resolve, reject);
        return;
      }
      existing.addEventListener('load', () => resolve(), { once: true });
      existing.addEventListener('error', () => {
        existing.remove();
        reject(new Error('高德地图 Loader 加载失败'));
      }, { once: true });
      if (window.AMapLoader) resolve();
      return;
    }
    const script = document.createElement('script');
    script.src = src;
    script.async = true;
    script.dataset.loadState = 'loading';
    script.onload = () => {
      script.dataset.loadState = 'loaded';
      resolve();
    };
    script.onerror = () => {
      script.remove();
      reject(new Error('高德地图 Loader 加载失败'));
    };
    document.head.appendChild(script);
  });
}

function pluginKey(plugins: AMapPlugin[]) {
  return plugins.slice().sort().join('|') || 'base';
}

function normalizeCity(value: AMapAddressComponent['city']) {
  if (Array.isArray(value)) {
    return value.filter(Boolean).join('/');
  }
  return String(value ?? '').trim() || undefined;
}

const GCJ_A = 6378245;
const GCJ_EE = 0.006693421622965943;

function outsideGcjChina(longitude: number, latitude: number) {
  return longitude < 72.004 || longitude > 137.8347 || latitude < 0.8293 || latitude > 55.8271;
}

function transformLatitude(longitude: number, latitude: number) {
  let value = -100 + 2 * longitude + 3 * latitude + 0.2 * latitude * latitude
    + 0.1 * longitude * latitude + 0.2 * Math.sqrt(Math.abs(longitude));
  value += (20 * Math.sin(6 * longitude * Math.PI) + 20 * Math.sin(2 * longitude * Math.PI)) * 2 / 3;
  value += (20 * Math.sin(latitude * Math.PI) + 40 * Math.sin(latitude / 3 * Math.PI)) * 2 / 3;
  value += (160 * Math.sin(latitude / 12 * Math.PI) + 320 * Math.sin(latitude * Math.PI / 30)) * 2 / 3;
  return value;
}

function transformLongitude(longitude: number, latitude: number) {
  let value = 300 + longitude + 2 * latitude + 0.1 * longitude * longitude
    + 0.1 * longitude * latitude + 0.1 * Math.sqrt(Math.abs(longitude));
  value += (20 * Math.sin(6 * longitude * Math.PI) + 20 * Math.sin(2 * longitude * Math.PI)) * 2 / 3;
  value += (20 * Math.sin(longitude * Math.PI) + 40 * Math.sin(longitude / 3 * Math.PI)) * 2 / 3;
  value += (150 * Math.sin(longitude / 12 * Math.PI) + 300 * Math.sin(longitude / 30 * Math.PI)) * 2 / 3;
  return value;
}

export function wgs84ToGcj02(longitude: number, latitude: number): [number, number] {
  if (!Number.isFinite(longitude) || !Number.isFinite(latitude) || outsideGcjChina(longitude, latitude)) {
    return [longitude, latitude];
  }
  const latitudeOffset = transformLatitude(longitude - 105, latitude - 35);
  const longitudeOffset = transformLongitude(longitude - 105, latitude - 35);
  const radianLatitude = latitude / 180 * Math.PI;
  const magic = 1 - GCJ_EE * Math.sin(radianLatitude) ** 2;
  const sqrtMagic = Math.sqrt(magic);
  const convertedLatitude = latitude + latitudeOffset * 180 / ((GCJ_A * (1 - GCJ_EE)) / (magic * sqrtMagic) * Math.PI);
  const convertedLongitude = longitude + longitudeOffset * 180 / (GCJ_A / sqrtMagic * Math.cos(radianLatitude) * Math.PI);
  return [convertedLongitude, convertedLatitude];
}

export function gcj02ToWgs84(longitude: number, latitude: number): [number, number] {
  if (!Number.isFinite(longitude) || !Number.isFinite(latitude) || outsideGcjChina(longitude, latitude)) {
    return [longitude, latitude];
  }
  let wgsLongitude = longitude;
  let wgsLatitude = latitude;
  for (let iteration = 0; iteration < 3; iteration += 1) {
    const [convertedLongitude, convertedLatitude] = wgs84ToGcj02(wgsLongitude, wgsLatitude);
    wgsLongitude -= convertedLongitude - longitude;
    wgsLatitude -= convertedLatitude - latitude;
  }
  return [wgsLongitude, wgsLatitude];
}

export function isValidAMapCoordinate(longitude: number, latitude: number) {
  return (
    Number.isFinite(longitude) &&
    Number.isFinite(latitude) &&
    longitude >= 73 &&
    longitude <= 135 &&
    latitude >= 18 &&
    latitude <= 54
  );
}

export function buildAMapMarkerURL({
  longitude,
  latitude,
  name
}: {
  longitude: number;
  latitude: number;
  name: string;
}) {
  const [mapLongitude, mapLatitude] = wgs84ToGcj02(longitude, latitude);
  return `https://uri.amap.com/marker?position=${mapLongitude},${mapLatitude}&name=${encodeURIComponent(name)}&src=lingniu-vehicle-platform`;
}

export function loadAMap(plugins: AMapPlugin[] = ['AMap.Scale']) {
  const config = getAMapConfig();
  if (!isAMapConfigured(config)) {
    return Promise.reject(new Error('高德地图未配置'));
  }
  const key = pluginKey(plugins);
  const cached = amapLoaderPromises.get(key);
  if (cached) {
    return cached;
  }
  if (config.securityServiceHost) {
    window._AMapSecurityConfig = { serviceHost: config.securityServiceHost };
  } else if (config.securityJsCode) {
    window._AMapSecurityConfig = { securityJsCode: config.securityJsCode };
  }
  const promise = loadScript('https://webapi.amap.com/loader.js')
    .then(() => {
      if (!window.AMapLoader) {
        throw new Error('高德地图 Loader 不可用');
      }
      return window.AMapLoader.load({
        key: config.webJsKey,
        version: '2.0',
        plugins
      }) as Promise<AMapLike>;
    })
    .catch((error: unknown) => {
      amapLoaderPromises.delete(key);
      throw error;
    });
  amapLoaderPromises.set(key, promise);
  return promise;
}

export async function reverseGeocodeWithAMap(longitude: number, latitude: number): Promise<AMapAddress> {
  if (!isValidAMapCoordinate(longitude, latitude)) {
    throw new Error('坐标不可用');
  }
  const [mapLongitude, mapLatitude] = wgs84ToGcj02(longitude, latitude);
  const AMap = await loadAMap(['AMap.Geocoder']);
  if (!AMap.Geocoder) {
    throw new Error('高德 Geocoder 插件不可用');
  }
  const geocoder = new AMap.Geocoder({ city: '全国' });
  return new Promise((resolve, reject) => {
    geocoder.getAddress([mapLongitude, mapLatitude], (status, result) => {
      if (status !== 'complete' || typeof result === 'string') {
        reject(new Error(typeof result === 'string' ? result : result?.info || '高德地址解析失败'));
        return;
      }
      const formattedAddress = String(result.regeocode?.formattedAddress ?? '').trim();
      if (!formattedAddress) {
        reject(new Error('高德地址解析返回空地址'));
        return;
      }
      const component = result.regeocode?.addressComponent ?? {};
      resolve({
        provider: 'AMap Web JS',
        formattedAddress,
        province: String(component.province ?? '').trim() || undefined,
        city: normalizeCity(component.city),
        district: String(component.district ?? '').trim() || undefined,
        township: String(component.township ?? '').trim() || undefined,
        adcode: String(component.adcode ?? '').trim() || undefined
      });
    });
  });
}
