import { Element, geometry, Group, ShapeOptions, Surface } from '@progress/kendo-drawing';

/**
 * @hidden
 */
export class Map {
    options: MapOptions;
    layers: any;
    element: HTMLElement;

    center(center?: Location | [number, number] | number[]): Location;
    extent(extent?: Extent): Extent;
    zoom(level?: number): number;
    viewSize(): any;

    constructor(element: any, options: MapOptions, theme: any, context: any);
    destroy(): void;

    eventOffset(e: any): geometry.Point;
    eventToLayer(e: any): geometry.Point;
    eventToLocation(e: any): geometry.Point;
    eventToView(e: any): geometry.Point;
    layerToLocation(point: geometry.Point | [number, number] | number[], zoom: number): Location;
    locationToLayer(location: Location | [number, number] | number[], zoom: number): geometry.Point;
    locationToView(location: Location | [number, number] | number[]): geometry.Point;
    resize(force?: boolean): void;
    setOptions(options: any): void;
    viewToLocation(point: geometry.Point | [number, number] | number[], zoom: number): Location;
    hideTooltip(): void;
}

/**
 * Represents a geographic location.
 */
export class Location {
    /**
     * The location latitude in decimal degrees.
     */
    lat: number;

    /**
     * The location longitude in decimal degrees.
     */
    lng: number;

    /**
     * Creates a new Location.
     *
     * @param lat The location latitude in decimal degrees.
     * @param lng The location longitude in decimal degrees.
     */
    constructor(lat: number, lng: number);

    /**
     * Creates a Location instance from an [longitude, latitude] array.
     *
     * @param lnglat The [longitude, latitude] array.
     * @returns The Location instance.
     */
    static fromLngLat(lnglat: [number, number] | number[]): Location;

    /**
     * Creates a Location instance from an [latitude, longitude] array.
     *
     * @param latlng The [latitude, longitude] array.
     * @returns The Location instance.
     */
    static fromLatLng(latlng: [number, number] | number[]): Location;

    /**
     * Creates a new instance with the same coordinates.
     *
     * @returns The new Location instance.
     */
    clone(): Location;

    /**
     * Finds a destination at the given distance and bearing from this location.
     *
     * @param distance The distance to the destination in meters.
     * @param bearing The initial bearing to the destination in decimal degrees.
     * @returns The destination at the given distance and bearing.
     */
    destination(distance: number, bearing: number): Location;

    /**
     * Calculates the [great-circle distance](https://en.wikipedia.org/wiki/Great-circle_distance) to the given destination in meters.
     *
     * @param destination The destination location.
     * @returns The distance to the specified location in meters.
     */
    distanceTo(destination: Location): number;

    /**
     * Compares this location with another instance.
     *
     * @param location The location to compare with.
     * @returns true if the location coordinates match; false otherwise.
     */
    equals(location: Location): boolean;

    /**
     * Rounds the location coordinates to the specified number of fractional digits.
     *
     * @param digits Number of fractional digits.
     * @returns The current Location instance.
     */
    round(digits: number): Location;

    /**
     * Returns the location coordinates as an [lat, lng] array.
     *
     * @returns An array representation of the location.
     */
    toArray(): [number, number];

    /**
     * Returns the location coordinates formatted as '{lat},{lng}'.
     *
     * @returns A string representation of the location.
     */
    toString(): string;

    /**
     * Wraps the latitude and longitude to fit into the [0, 90] and [0, 180] range.
     *
     * @returns The current Location instance.
     */
    wrap(): Location;
}

/**
 * Represents a geographic region defined by two extreme locations (North West and South East).
 */
export class Extent {
    /**
     * The North West extreme location.
     */
    nw: Location;

    /**
     * The South East extreme location.
     */
    se: Location;

    /**
     * Creates a new Extent.
     *
     * @param nw The North West extreme location.
     * @param se The South East extreme location.
     */
    constructor(
        nw: Location | [number, number] | number[],
        se: Location | [number, number] | number[]
    );

    /**
     * Tests if a location is contained within the extent.
     *
     * @param location The location to test for.
     * @returns true if the extent contains the location, false otherwise.
     */
    contains(location: Location): boolean;

    /**
     * Tests if any of the locations is contained within the extent.
     *
     * @param locations An array of locations to test for.
     * @returns true if the extent contains any of the locations, false otherwise.
     */
    containsAny(locations: Location[]): boolean;

    /**
     * Returns the center of the extent.
     *
     * @returns The extent center location.
     */
    center(): Location;

    /**
     * Grows the extent, if required, to contain the specified location.
     *
     * @param location The location to include in the extent.
     */
    include(location: Location): void;

    /**
     * Grows the extent, if required, to contain all specified locations.
     *
     * @param locations The locations to include in the extent.
     */
    includeAll(locations: any): void;

    /**
     * Returns the four extreme locations of the extent.
     *
     * @returns An object with nw, ne, se and sw locations.
     */
    edges(): { nw: Location, ne: Location, se: Location, sw: Location};

    /**
     * Returns the four extreme locations of the extent as an array.
     *
     * @returns An array with [NW, NE, SE, SW] locations.
     */
    toArray(): [Location, Location, Location, Location];

    /**
     * Tests if the given extent overlaps with this instance.
     *
     * @param extent The extent to test with.
     * @returns true if the extents overlap, false otherwise.
     */
    overlaps(extent: Extent): boolean;
}


/**
 * @hidden
 */
interface MapOptions {
    /**
     * The map center. Coordinates are listed as `[Latitude, Longitude]`.
     */
    center?: Location | [number, number] | number[];

    /**
     * The configuration of built-in map controls.
     */
    controls?: MapControls;

    /**
     * The minimum zoom level. Typical web maps use zoom levels from 0 (whole world) to 19 (sub-meter features).
     *
     * @default 1
     */
    minZoom?: number;

    /**
     * The maximum zoom level. Typical web maps use zoom levels from 0 (whole world) to 19 (sub-meter features).
     *
     * @default 19
     */
    maxZoom?: number;

    /**
     * The size of the map in pixels at zoom level 0.
     *
     * @default 256
     */
    minSize?: number;

    /**
     * Controls whether the user can pan the map.
     *
     * @default true
     */
    pannable?: boolean;

    /**
     * Specifies whether the map should wrap around the east-west edges.
     *
     * @default true
     */
    wraparound?: boolean;

    /**
     * The initial zoom level.
     *
     * Typical web maps use zoom levels from 0 (whole world) to 19 (sub-meter features).
     * The map size is derived from the zoom level and minScale options: size = (2 ^ zoom) * minSize
     *
     * > Map zoom rounds floating point numbers. This is done so as the majority of web maps use the whole [zoom levels](https://wiki.openstreetmap.org/wiki/Zoom_levels) 0 through to 19.
     *
     * @default 3
     */
    zoom?: number;

    /**
     * Controls whether the map zoom level can be changed by the user.
     *
     * @default true
     */
    zoomable?: boolean;
}

/**
 * @hidden
*/
export type MapLayerType = 'tile' | 'marker' | 'shape' | 'bubble';

/**
 * Map layer configuration options.
 * @hidden
 */
export interface MapLayerOptions {
    /**
     * The attribution for the layer. Accepts HTML.
     */
    attribution?: string;

    /**
     * Specifies the extent of the region covered by this layer.
     * The layer will be hidden when the specified area is out of view. If not specified, the layer is always visible.
     *
     * Accepts an `Extent` or an array that specifies the extent covered by this layer:
     * `[[NW lat, NW long], [SE lat, SE long]]`.
     */
    extent?: Extent | [[number, number], [number, number]];

    /**
     * The minimum zoom level at which to show this layer.
     */
    maxZoom?: number;

    /**
     * The minimum zoom level at which to show this layer.
     */
    minZoom?: number;

    /**
     * The the opacity for the layer.
     *
     * The value must be in the range from 0 (fully transparent) to 1 (fully opaque).
     */
    opacity?: number;

    /**
     * The zIndex for this layer.
     *
     * Layers are normally stacked in declaration order (last one is on top).
     */
    zIndex?: number;
}

/**
 * The arguments to the `symbol` function on bubble layers.
 */
export interface BubbleLayerSymbolArgs {
    /**
     * The symbol center on the current layer.
     */
    center: geometry.Point;

    /**
     * The symbol size.
     */
    size: number;

    /**
     * The symbol style.
     */
    style: ShapeOptions;

    /**
     * The dataItem used to create the symbol.
     */
    dataItem: any;

    /**
     * The location of the data point.
     */
    location: Location;
}

/**
 * Bubble layer symbol type.
 */
export type BubbleLayerSymbol = 'circle' | 'square' | ((args: BubbleLayerSymbolArgs) => Element);

/**
 * Configuration options for the map Bubble layer.
 */
export interface BubbleLayerOptions extends MapLayerOptions {
    /**
     * The array of data items for this layer.
     */
    data?: any[];

    /**
     * The data item field which contains the symbol location.
     *
     * The field should be an array with two numbers - latitude and longitude in decimal degrees.
     */
    locationField?: string;

    /**
     * The value field for the symbols used to determine their relative size.
     * The data item field should be a number.
     */
    valueField?: string;

    /**
     * The symbol to use for bubble layers.
     */
    symbol?: BubbleLayerSymbol;

    /**
     * The default style for symbols.
     */
    style?: ShapeOptions;

    /**
     * The maximum symbol size for bubble layer symbols.
     *
     * @default 100
     */
    maxSize?: number;

    /**
     * The minimum symbol size for bubble layer symbols.
     *
     * > Setting non-zero value will distort symbol area to value ratio.
     *
     * @default 0
     */
    minSize?: number;
}

/**
 * Configuration options for the map Marker layer.
 */
export interface MarkerLayerOptions extends MapLayerOptions {
    /**
     * The array of data items for this layer.
     */
    data?: any[];

    /**
     * The data item field which contains the marker location.
     *
     * The field should be an array with two numbers - latitude and longitude in decimal degrees.
     */
    locationField?: string;

    /**
     * The data item field which contains the marker title.
     */
    titleField?: string;

    /**
     * The default marker shape for data-bound markers.
     *
     * The following pre-defined marker shapes are available:
     * * "pinTarget"
     * * "pin"
     *
     * Marker shapes are implemented as CSS classes on the marker element (span.k-marker). For example "pinTarget" is rendered as "k-marker-pin-target".
     */
    shape?: string | 'pinTarget' | 'pin';
}

/**
 * Configuration options for the map Shape layer.
 */
export interface ShapeLayerOptions extends MapLayerOptions {
    /**
     * The array of data items for this layer.
     */
    data?: any[];

    /**
     * The default style for shapes.
     */
    style?: ShapeOptions;
}

/**
 * The arguments to the `urlTemplate` function on tile layers.
 */
export interface TileUrlTemplateArgs {
    /**
     * X coordinate of the tile.
     */
    x: number;

    /**
     * Y coordinate of the tile.
     */
    y: number;

    /**
     * The zoom level.
     */
    zoom: number;

    /**
     * Subdomain for this tile.
     */
    subdomain: string;
}

/**
 * Configuration options for the map Tile layer.
 */
export interface TileLayerOptions extends MapLayerOptions {
    /**
     * The size of the image tile in pixels.
     *
     * @default 256
     */
    tileSize?: number;

    /**
     * A list of subdomains to use for loading tiles.
     *
     * Alternating between different subdomains allows more requests to be executed in parallel.
     */
    subdomains?: string[];

    /**
     * A function that returns an image URL for each tile position.
     *
     * @return the URL for the tile.
     */
    urlTemplate: (args: TileUrlTemplateArgs) => string;
}

/**
 * Possible positions of map controls
 */
export type MapControlsPosition = 'topLeft' | 'topRight' | 'bottomRight' | 'bottomLeft';

/**
 * The configuration of built-in map controls.
 */
export interface MapControls {
    /**
     * Configures or disables the built-in attribution control.
     *
     * @default true
     */
    attribution?: boolean | { position?: MapControlsPosition };

    /**
     * Configures or disables the built-in navigator control (directional pad).
     *
     * @default true
     */
    navigator?: boolean | { position?: MapControlsPosition };

    /**
     * Configures or disables the built-in zoom control (+/- button).
     *
     * @default true
     */
    zoom?: boolean | { position?: MapControlsPosition };
}

/**
 * An instance of a Map layer
 */
export interface MapLayer {
    /**
     * The layer configuration settings.
     */
    options: MapLayerOptions;

    /**
     * The drawing surface for Shape layers.
     */
    surface?: Surface;

    /**
     * The marker items for Marker layers.
     */
    items?: MapMarker[];

    /**
     * Shows the layer, if not visible.
     */
    show(): void;

    /**
     * Hides the layer, if visible.
     */
    hide(): void;
}

/**
 * Represents a Map marker with title and location.
 */
export interface MapMarker {
    /**
     * Gets or sets the Marker location.
     *
     * @param value The marker location on the map. Coordinates are listed as `[Latitude, Longitude]`.
     * @return The marker location.
     */
    location(value?: number[] | Location): Location;

    /**
     * The data item used to create the marker.
     */
    dataItem: any;

    /**
     * The marker shape.
     */
    shape: string;

    /**
     * The marker title. Displayed as browser tooltip.
     */
    title: string;
}

/**
 * Fired immediately before the map is reset. This event is typically used for cleanup by layer implementers.
 * @hidden
 */
export interface MapBeforeResetEvent {
}

/**
 * Fired when the user clicks on the map.
 * @hidden
 */
export interface MapClickEvent {
    /**
     * The location of the clicked point.
     */
    location: Location;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fired when a marker has been displayed and has a DOM element assigned.
 * @hidden
 */
export interface MapMarkerActivateEvent {
    /**
     * The marker instance.
     */
    marker: MapMarker;

    /**
     * The marker layer instance.
     */
    layer: MapLayer;
}

/**
 * Fired when a marker has been created and is about to be displayed.
 *
 * Cancelling the event will prevent the marker from being shown.
 * @hidden
 */
export interface MapMarkerCreatedEvent {
    /**
     * The marker instance.
     */
    marker: MapMarker;

    /**
     * The marker layer instance.
     */
    layer: MapLayer;

    /**
     * Prevents the default action for a specified event.
     */
    preventDefault(): void;
}

/**
 * Fired when a marker has been clicked or tapped.
 * @hidden
 */
export interface MapMarkerClickEvent {
    /**
     * The marker instance.
     */
    marker: MapMarker;

    /**
     * The marker layer instance.
     */
    layer: MapLayer;
}

/**
 * Fired while the map viewport is being moved.
 * @hidden
 */
export interface MapPanEvent {
    /**
     * The map origin (top left or NW corner).
     */
    origin: Location;

    /**
     * The current map center.
     */
    center: Location;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fires after the map viewport has been moved.
 * @hidden
 */
export interface MapPanEndEvent {
    /**
     * The map origin (top left or NW corner).
     */
    origin: Location;

    /**
     * The current map center.
     */
    center: Location;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fired when the map is reset.
 *
 * This typically occurs on initial load and after a zoom/center change.
 * @hidden
 */
export interface MapResetEvent {
}

/**
 * Fired when a shape is clicked or tapped.
 * @hidden
 */
export interface MapShapeClickEvent {
    /**
     * The shape layer instance.
     */
    layer: MapLayer;

    /**
     * The shape instance.
     */
    shape: Element;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fired when a shape is created, but is not rendered yet.
 * @hidden
 */
export interface MapShapeCreatedEvent {
    /**
     * The shape layer instance.
     */
    layer: MapLayer;

    /**
     * The shape instance.
     */
    shape: Element;

    /**
     * The original data item for this Shape.
     */
    dataItem: any;

    /**
     * The shape location
     */
    location: Location;
}

/**
 * Fired when a [GeoJSON Feature](https://geojson.org/geojson-spec.html#feature-objects) is created on a shape layer.
 * @hidden
 */
export interface MapShapeFeatureCreatedEvent {
    /**
     * The original data item for this Feature. Members include `geometries` and `properties`.
     */
    dataItem: any;

    /**
     * The shape layer instance.
     */
    layer: MapLayer;

    /**
     * The group containing feature shape instances.
     */
    group: Group;

    /**
     * A reference to the `dataItem.properties` object.
     */
    properties: any;
}

/**
 * Fired when the mouse enters a shape.
 *
 * > This event will fire reliably only for shapes that have set fill color.
 * > The opacity can still be set to 0 so the shapes appear to have no fill.
 * @hidden
 */
export interface MapShapeMouseEnterEvent {
    /**
     * The shape layer instance.
     */
    layer: MapLayer;

    /**
     * The shape instance.
     */
    shape: Element;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fired when the mouse leaves a shape.
 *
 * > This event will fire reliably only for shapes that have set fill color.
 * > The opacity can still be set to 0 so the shapes appear to have no fill.
 * @hidden
 */
export interface MapShapeMouseLeaveEvent {
    /**
     * The shape layer instance.
     */
    layer: MapLayer;

    /**
     * The shape instance.
     */
    shape: Element;

    /**
     * The source DOM event instance
     */
    originalEvent: any;
}

/**
 * Fired when the map zoom level is about to change.
 *
 * Cancelling the event will prevent the user action.
 * @hidden
 */
export interface MapZoomStartEvent {
    /**
     * The source DOM event instance
     */
    originalEvent: any;

    /**
     * Prevents the default action for a specified event.
     */
    preventDefault(): void;
}

/**
 * Fired when the map zoom level has changed.
 * @hidden
 */
export interface MapZoomEndEvent {
    /**
     * The source DOM event instance
     */
    originalEvent: any;
}
