import { useRef, useState, useEffect } from 'react';

import { useTranslation } from 'react-i18next';

import MapView from '@arcgis/core/views/MapView';
import esriConfig from '@arcgis/core/config.js';
import * as print from '@arcgis/core/rest/print.js';
import PrintParameters from '@arcgis/core/rest/support/PrintParameters.js';
import PrintTemplate from '@arcgis/core/rest/support/PrintTemplate.js';
import * as reactiveUtils from '@arcgis/core/core/reactiveUtils.js';
import Point from '@arcgis/core/geometry/Point.js';

// set assets path for ArcGIS Maps SDK widgets
esriConfig.assetsPath = './assets';

// set local assets for Calcite components
import { setAssetPath } from '@esri/calcite-components/dist/components';
setAssetPath(window.location.href);

// import Calcite components
import '@esri/calcite-components/dist/components/calcite-button';
import '@esri/calcite-components/dist/components/calcite-panel';
import '@esri/calcite-components/dist/components/calcite-input-text';
import '@esri/calcite-components/dist/components/calcite-label';
import '@esri/calcite-components/dist/components/calcite-select';
import '@esri/calcite-components/dist/components/calcite-option';
import '@esri/calcite-components/dist/components/calcite-progress';
import {
  CalciteButton,
  CalcitePanel,
  CalciteInputText,
  CalciteLabel,
  CalciteSelect,
  CalciteOption,
  CalciteProgress,
} from '@esri/calcite-components-react';

// helper function to clamp coordinates to visible area
function clamp(value: number, from: number, to: number) {
  return value < from ? from : value > to ? to : value;
}

// print service URL
const printURL = 'https://gis.geosphere.at/maps/rest/services/tools/printing/GPServer/Export%20Web%20Map';

// available scales
const scales = [10000, 25000, 50000, 100000, 200000, 500000, 1000000, 3500000];

// available formats
interface Format {
  [formatName: string]: number[];
}

// dimensions width and height in mm
const formats: Format = {
  'A4 Hochformat': [190, 265],
  'A4 Hochformat mit Legende': [190, 225],
  'A4 Querformat': [277, 178],
  'A4 Querformat mit Legende': [277, 140],
  'A3 Hochformat': [277, 385],
  'A3 Hochformat mit Legende': [277, 335],
  'A3 Querformat': [400, 264],
  'A3 Querformat mit Legende': [400, 215],
};

export default function Print({ view, maskRoot }: { view: MapView; maskRoot: HTMLDivElement }) {
  const { t } = useTranslation();

  const [title, setTitle] = useState<string>('GeoSphere Austria');
  const [format, setFormat] = useState<string>(Object.keys(formats)[0]);
  const [scale, setScale] = useState<number>(scales[0]);
  const [printing, setPrinting] = useState<boolean>(false);

  const rendered = useRef<boolean>(false);

  const currentScale = useRef<number>();
  currentScale.current = scale;

  const currentFormat = useRef<string>();
  currentFormat.current = format;

  const controllerRef = useRef<AbortController | null>(null);

  const handlePrint = () => {
    setPrinting(true);

    const template = new PrintTemplate({
      layout: format,
      format: 'pdf',
      layoutOptions: {
        titleText: title,
        scalebarUnit: 'Kilometers',
        customTextElements: [],
      },
      exportOptions: {
        dpi: 96,
      },
      scalePreserved: true,
      outScale: scale,
    });

    const params = new PrintParameters({
      view,
      template,
    });

    controllerRef.current = new AbortController();
    print.execute(printURL, params, { signal: controllerRef.current.signal }).then(printResult).catch(printError);
  };

  function printResult(result: any) {
    const filename = 'GeoSphere_Maps_Print.pdf';
    fetch(result.url)
      .then((res) => res.blob())
      .then((blob) => {
        const element = document.createElement('a');
        element.setAttribute('href', URL.createObjectURL(blob));
        element.setAttribute('download', filename);
        document.body.appendChild(element);
        element.click();
        document.body.removeChild(element);
        setPrinting(false);
      });
  }

  function printError(err: any) {
    if (err.name === 'AbortError') {
      // console.log('Request aborted');
    } else {
      console.error('Error encountered: ', err);
    }
  }

  const handleTitleChange = (event: any) => {
    setTitle(event.target.value);
  };

  const handleFormatChange = (event: any) => {
    const newFormat = event.target.value;
    setFormat(newFormat);
    updatePreview(scale, newFormat);
  };

  const handleScaleChange = (event: any) => {
    const newScale = parseInt(event.target.value);
    setScale(newScale);
    updatePreview(newScale, format);
  };

  const updatePreview = (newScale: number, newFormat: string) => {
    const width = (formats[newFormat][0] * newScale) / 1000;
    const height = (formats[newFormat][1] * newScale) / 1000;
    setMask(width, height);
  };

  // set the mask for print preview
  const setMask = (width: number, height: number) => {
    const center = view.center;
    if (center) {
      const xmin = center.x - width / 2;
      const xmax = center.x + width / 2;
      const ymin = center.y - height / 2;
      const ymax = center.y + height / 2;

      const upperLeft = view.toScreen(
        new Point({
          x: xmin,
          y: ymax,
          spatialReference: view.spatialReference,
        })
      );

      const lowerRight = view.toScreen(
        new Point({
          x: xmax,
          y: ymin,
          spatialReference: view.spatialReference,
        })
      );

      const left = clamp(Math.round(upperLeft.x), 0, view.width);
      const top = clamp(Math.round(upperLeft.y), 0, view.height);
      const maskWidth = clamp(Math.round(lowerRight.x - upperLeft.x), 0, view.width);
      const maskHeight = clamp(Math.round(lowerRight.y - upperLeft.y), 0, view.height);

      if (maskRoot) {
        maskRoot.style.left = left + 'px';
        maskRoot.style.top = top + 'px';
        maskRoot.style.width = maskWidth + 'px';
        maskRoot.style.height = maskHeight + 'px';
      }
    }
  };

  useEffect(() => {
    if (currentScale.current && currentFormat.current) {
      updatePreview(currentScale.current, currentFormat.current);
    }

    let handle: any;
    if (!rendered.current) {
      handle = reactiveUtils.watch(
        () => view?.extent,
        () => {
          if (currentScale.current && currentFormat.current) {
            updatePreview(currentScale.current, currentFormat.current);
          }
        }
      );
    }

    rendered.current = true;
    return () => {
      handle?.remove();
    };
  });

  const handleAbort = () => {
    if (controllerRef.current) {
      controllerRef.current.abort();
      setPrinting(false);
    }
  };

  return (
    <CalcitePanel heading={t('print.sub-heading')} className="px-3">
      <div>{printing && <CalciteProgress type="indeterminate"></CalciteProgress>}</div>
      <CalciteLabel className="mt-5 mx-5">
        {t('print.titleLabel')}
        <CalciteInputText
          placeholder={t('print.titleLabel')}
          onCalciteInputTextInput={handleTitleChange}
          className="mx-0"
        ></CalciteInputText>
      </CalciteLabel>
      <CalciteLabel className="mx-5">
        {t('print.formatLabel')}
        <CalciteSelect label="format" onCalciteSelectChange={handleFormatChange}>
          {Object.keys(formats).map((formatName) => {
            return (
              <CalciteOption key={`${formatName}`} value={`${formatName}`}>
                {t(`print.formats.${formatName}`)}
              </CalciteOption>
            );
          })}
        </CalciteSelect>
      </CalciteLabel>

      <CalciteLabel className="mx-5">
        {t('print.scaleLabel')}
        <CalciteSelect label="scale" onCalciteSelectChange={handleScaleChange}>
          {scales.map((num) => (
            <CalciteOption value={`${num}`} key={num}>{`1:${num
              .toString()
              .match(/(\d+?)(?=(\d{3})+(?!\d)|$)/g)
              ?.join('.')}`}</CalciteOption>
          ))}
        </CalciteSelect>
      </CalciteLabel>

      {!printing ? (
        <CalciteButton width="half" slot="footer" onClick={handlePrint}>
          {t('print.button.print')}
        </CalciteButton>
      ) : (
        <>
          <CalciteButton width="half" slot="footer" onClick={handleAbort}>
            {t('print.button.cancel')}
          </CalciteButton>
        </>
      )}
    </CalcitePanel>
  );
}
