// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {ImageType} from '@loaders.gl/images';
import type {
  Source,
  DataSourceOptions,
  ImageSourceMetadata,
  GetImageParameters
} from '@loaders.gl/loader-utils';
import {DataSource, ImageSource} from '@loaders.gl/loader-utils';

export type ArcGISImageSourceProps = DataSourceOptions & {
  'arcgis-image-server'?: {
    // TODO - add options here
  };
};

export const ArcGISImageServerSource = {
  name: 'ArcGISImageServer',
  id: 'arcgis-image-server',
  module: 'wms',
  version: '0.0.0',
  extensions: [],
  mimeTypes: [],
  type: 'arcgis-image-server',
  fromUrl: true,
  fromBlob: false,

  defaultOptions: {
    'arcgis-image-server': {
      // TODO - add options here
    }
  },

  testURL: (url: string): boolean => url.toLowerCase().includes('ImageServer'),
  createDataSource: (url, props: ArcGISImageSourceProps): ArcGISImageSource =>
    new ArcGISImageSource(url as string, props)
} as const satisfies Source<ArcGISImageSource>;

/**
 * ArcGIS ImageServer
 * Note - exports a big API, that could be exposed here if there is a use case
 * @see https://developers.arcgis.com/rest/services-reference/enterprise/image-service.htm
 */
export class ArcGISImageSource
  extends DataSource<string, ArcGISImageSourceProps>
  implements ImageSource
{
  constructor(url: string, props: ArcGISImageSourceProps) {
    super(url, props, ArcGISImageServerSource.defaultOptions);
  }

  // ImageSource (normalized endpoints)

  async getMetadata(): Promise<ImageSourceMetadata> {
    return (await this.metadata()) as ImageSourceMetadata;
    // TODO - normalize metadata
  }

  async getImage(parameters: GetImageParameters): Promise<ImageType> {
    throw new Error('not implemented');
    // TODO - Map generic parameters to ArcGIS specific parameters
    // return await this.exportImage(parameters);
  }

  // ImageServer endpoints

  async metadata(): Promise<unknown> {
    // We just need a JSON parsing...
    // return this.getUrl({path: '', ...options});
    throw new Error('not implemented');
  }

  /** 
   * Form a URL to an ESRI ImageServer
   // https://sampleserver6.arcgisonline.com/arcgis/rest/services/NLCDLandCover2001/ImageServer/exportImage?bbox=${bounds[0]},${bounds[1]},${bounds[2]},${bounds[3]}&bboxSR=4326&size=${width},${height}&imageSR=102100&time=&format=jpgpng&pixelType=U8&noData=&noDataInterpretation=esriNoDataMatchAny&interpolation=+RSP_NearestNeighbor&compression=&compressionQuality=&bandIds=&mosaicRule=&renderingRule=&f=image`,
   */
  exportImage(options: {
    boundingBox: [number, number, number, number];
    boundingBoxSR?: string;
    width: number;
    height: number;
    imageSR?: string;
    time?: never;
    format?: 'jpgpng';
    pixelType?: 'U8';
    noData?: never;
    noDataInterpretation?: 'esriNoDataMatchAny';
    interpolation?: '+RSP_NearestNeighbor';
    compression?: never;
    compressionQuality?: never;
    bandIds?: never;
    mosaicRule?: never;
    renderingRule?: never;
    f?: 'image';
  }): Promise<ImageType> {
    // See WMSService.getMap()
    throw new Error('not implemented');
  }

  // URL creators

  metadataURL(options: {parameters?: Record<string, unknown>}): string {
    return `${this.url}?f=pjson`;
  }

  /** 
   * Form a URL to an ESRI ImageServer
   // https://sampleserver6.arcgisonline.com/arcgis/rest/services/NLCDLandCover2001/ImageServer/exportImage?
   //   bbox=${bounds[0]},${bounds[1]},${bounds[2]},${bounds[3]}&bboxSR=4326&
   //   size=${width},${height}&imageSR=102100&time=&format=jpgpng&pixelType=U8&
   //   noData=&noDataInterpretation=esriNoDataMatchAny&interpolation=+RSP_NearestNeighbor&compression=&
   //   compressionQuality=&bandIds=&mosaicRule=&renderingRule=&
   //   f=image
   */
  exportImageURL(options: {
    bbox: [number, number, number, number];
    boxSR?: string;
    width: number;
    height: number;
    imageSR?: string;
    time?: never;
    format?: 'jpgpng';
    pixelType?: 'U8';
    noData?: never;
    noDataInterpretation?: 'esriNoDataMatchAny';
    interpolation?: '+RSP_NearestNeighbor';
    compression?: never;
    compressionQuality?: never;
    bandIds?: never;
    mosaicRule?: never;
    renderingRule?: never;
    f?: 'image';
  }): string {
    const bbox = `bbox=${options.bbox[0]},${options.bbox[1]},${options.bbox[2]},${options.bbox[3]}`;
    const size = `size=${options.width},${options.height}`;
    const arcgisOptions = {...options, bbox, size};
    // @ts-expect-error
    delete arcgisOptions.width;
    // @ts-expect-error
    delete arcgisOptions.height;
    return this.getUrl('exportImage', arcgisOptions);
  }

  // INTERNAL METHODS

  /**
   * @note protected, since perhaps getWMSUrl may need to be overridden to handle certain backends?
   * @note if override is common, maybe add a callback prop?
   * */
  protected getUrl(
    path: string,
    options: Record<string, unknown>,
    extra?: Record<string, unknown>
  ): string {
    let url = `${this.url}/${path}`;
    let first = true;
    for (const [key, value] of Object.entries(options)) {
      url += first ? '?' : '&';
      first = false;
      if (Array.isArray(value)) {
        url += `${key.toUpperCase()}=${value.join(',')}`;
      } else {
        url += `${key.toUpperCase()}=${value ? String(value) : ''}`;
      }
    }
    return url;
  }

  /** Checks for and parses a WMS XML formatted ServiceError and throws an exception */
  protected async checkResponse(response: Response) {
    if (!response.ok) {
      // } || response.headers['content-type'] === WMSErrorLoader.mimeTypes[0]) {
      // const arrayBuffer = await response.arrayBuffer();
      // const error = await WMSErrorLoader.parse(arrayBuffer, this.loadOptions);
      throw new Error('error');
    }
  }
}
