/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Class = require('../../utils/Class');
var CONST = require('../const');
var File = require('../File');
var FileTypesManager = require('../FileTypesManager');
var GetFastValue = require('../../utils/object/GetFastValue');
var IsPlainObject = require('../../utils/object/IsPlainObject');
var GetURL = require('../GetURL');

/**
 * @classdesc
 * A single Image File suitable for loading by the Loader.
 *
 * These are created when you use the Phaser.Loader.LoaderPlugin#image method and are not typically created directly.
 *
 * For documentation about what all the arguments and configuration options mean please see Phaser.Loader.LoaderPlugin#image.
 *
 * @class ImageFile
 * @extends Phaser.Loader.File
 * @memberof Phaser.Loader.FileTypes
 * @constructor
 * @since 3.0.0
 *
 * @param {Phaser.Loader.LoaderPlugin} loader - A reference to the Loader that is responsible for this file.
 * @param {(string|Phaser.Types.Loader.FileTypes.ImageFileConfig)} key - The key to use for this file, or a file configuration object.
 * @param {string|string[]} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - Extra XHR Settings specifically for this file.
 * @param {Phaser.Types.Loader.FileTypes.ImageFrameConfig} [frameConfig] - The frame configuration object. Only provided for, and used by, Sprite Sheets.
 */
var ImageFile = new Class({

    Extends: File,

    initialize:

    function ImageFile (loader, key, url, xhrSettings, frameConfig)
    {
        var extension = 'png';
        var normalMapURL;

        if (IsPlainObject(key))
        {
            var config = key;

            key = GetFastValue(config, 'key');
            url = GetFastValue(config, 'url');
            normalMapURL = GetFastValue(config, 'normalMap');
            xhrSettings = GetFastValue(config, 'xhrSettings');
            extension = GetFastValue(config, 'extension', extension);
            frameConfig = GetFastValue(config, 'frameConfig');
        }

        if (Array.isArray(url))
        {
            normalMapURL = url[1];
            url = url[0];
        }

        var fileConfig = {
            type: 'image',
            cache: loader.textureManager,
            extension: extension,
            responseType: 'blob',
            key: key,
            url: url,
            xhrSettings: xhrSettings,
            config: frameConfig
        };

        File.call(this, loader, fileConfig);

        //  Do we have a normal map to load as well?
        if (normalMapURL)
        {
            var normalMap = new ImageFile(loader, this.key, normalMapURL, xhrSettings, frameConfig);

            normalMap.type = 'normalMap';

            this.setLink(normalMap);

            loader.addFile(normalMap);
        }

        this.useImageElementLoad = loader.imageLoadType === 'HTMLImageElement';

        if (this.useImageElementLoad)
        {
            this.load = this.loadImage;
            this.onProcess = this.onProcessImage;
        }
    },

    /**
     * Called automatically by Loader.nextFile.
     * This method controls what extra work this File does with its loaded data.
     *
     * @method Phaser.Loader.FileTypes.ImageFile#onProcess
     * @since 3.7.0
     */
    onProcess: function ()
    {
        this.state = CONST.FILE_PROCESSING;

        this.data = new Image();

        this.data.crossOrigin = this.crossOrigin;

        var _this = this;

        this.data.onload = function ()
        {
            File.revokeObjectURL(_this.data);

            _this.onProcessComplete();
        };

        this.data.onerror = function ()
        {
            File.revokeObjectURL(_this.data);

            _this.onProcessError();
        };

        File.createObjectURL(this.data, this.xhrLoader.response, 'image/png');
    },

    /**
     * Handles image load processing.
     *
     * @method Phaser.Loader.FileTypes.ImageFile#onProcessImage
     * @private
     * @since 3.60.0
     */
    onProcessImage: function ()
    {
        var result = this.state;

        this.state = CONST.FILE_PROCESSING;

        if (result === CONST.FILE_LOADED)
        {
            this.onProcessComplete();
        }
        else
        {
            this.onProcessError();
        }
    },

    /**
     * Loads the image using either XHR or an Image tag.
     *
     * @method Phaser.Loader.FileTypes.ImageFile#loadImage
     * @private
     * @since 3.60.0
     */
    loadImage: function ()
    {
        this.state = CONST.FILE_LOADING;

        this.src = GetURL(this, this.loader.baseURL);

        if (this.src.indexOf('data:') === 0)
        {
            console.warn('Local data URIs are not supported: ' + this.key);
        }
        else
        {
            this.data = new Image();

            this.data.crossOrigin = this.crossOrigin;

            var _this = this;

            this.data.onload = function ()
            {
                _this.state = CONST.FILE_LOADED;

                _this.loader.nextFile(_this, true);
            };

            this.data.onerror = function ()
            {
                _this.loader.nextFile(_this, false);
            };

            this.data.src = this.src;
        }
    },

    /**
     * Adds this file to its target cache upon successful loading and processing.
     *
     * @method Phaser.Loader.FileTypes.ImageFile#addToCache
     * @since 3.7.0
     */
    addToCache: function ()
    {
        var linkFile = this.linkFile;

        if (linkFile && linkFile.state >= CONST.FILE_COMPLETE)
        {
            if (this.type === 'image')
            {
                this.cache.addImage(this.key, this.data, linkFile.data);
            }
            else
            {
                this.cache.addImage(linkFile.key, linkFile.data, this.data);
            }
        }
        else if (!linkFile)
        {
            this.cache.addImage(this.key, this.data);
        }
    }

});

/**
 * Adds an Image, or array of Images, to the current load queue.
 *
 * You can call this method from within your Scene's `preload`, along with any other files you wish to load:
 *
 * ```javascript
 * function preload ()
 * {
 *     this.load.image('logo', 'images/phaserLogo.png');
 * }
 * ```
 *
 * The file is **not** loaded right away. It is added to a queue ready to be loaded either when the loader starts,
 * or if it's already running, when the next free load slot becomes available. This happens automatically if you
 * are calling this from within the Scene's `preload` method, or a related callback. Because the file is queued
 * it means you cannot use the file immediately after calling this method, but must wait for the file to complete.
 * The typical flow for a Phaser Scene is that you load assets in the Scene's `preload` method and then when the
 * Scene's `create` method is called you are guaranteed that all of those assets are ready for use and have been
 * loaded.
 *
 * Phaser can load all common image types: png, jpg, gif and any other format the browser can natively handle.
 * If you try to load an animated gif only the first frame will be rendered. Browsers do not natively support playback
 * of animated gifs to Canvas elements.
 *
 * The key must be a unique String. It is used to add the file to the global Texture Manager upon a successful load.
 * The key should be unique both in terms of files being loaded and files already present in the Texture Manager.
 * Loading a file using a key that is already taken will result in a warning. If you wish to replace an existing file
 * then remove it from the Texture Manager first, before loading a new one.
 *
 * Instead of passing arguments you can pass a configuration object, such as:
 *
 * ```javascript
 * this.load.image({
 *     key: 'logo',
 *     url: 'images/AtariLogo.png'
 * });
 * ```
 *
 * See the documentation for `Phaser.Types.Loader.FileTypes.ImageFileConfig` for more details.
 *
 * Once the file has finished loading you can use it as a texture for a Game Object by referencing its key:
 *
 * ```javascript
 * this.load.image('logo', 'images/AtariLogo.png');
 * // and later in your game ...
 * this.add.image(x, y, 'logo');
 * ```
 *
 * If you have specified a prefix in the loader, via `Loader.setPrefix` then this value will be prepended to this files
 * key. For example, if the prefix was `MENU.` and the key was `Background` the final key will be `MENU.Background` and
 * this is what you would use to retrieve the image from the Texture Manager.
 *
 * The URL can be relative or absolute. If the URL is relative the `Loader.baseURL` and `Loader.path` values will be prepended to it.
 *
 * If the URL isn't specified the Loader will take the key and create a filename from that. For example if the key is "alien"
 * and no URL is given then the Loader will set the URL to be "alien.png". It will always add `.png` as the extension, although
 * this can be overridden if using an object instead of method arguments. If you do not desire this action then provide a URL.
 *
 * Phaser also supports the automatic loading of associated normal maps. If you have a normal map to go with this image,
 * then you can specify it by providing an array as the `url` where the second element is the normal map:
 *
 * ```javascript
 * this.load.image('logo', [ 'images/AtariLogo.png', 'images/AtariLogo-n.png' ]);
 * ```
 *
 * Or, if you are using a config object use the `normalMap` property:
 *
 * ```javascript
 * this.load.image({
 *     key: 'logo',
 *     url: 'images/AtariLogo.png',
 *     normalMap: 'images/AtariLogo-n.png'
 * });
 * ```
 *
 * The normal map file is subject to the same conditions as the image file with regard to the path, baseURL, CORs and XHR Settings.
 * Normal maps are a WebGL only feature.
 *
 * In Phaser 3.60 a new property was added that allows you to control how images are loaded. By default, images are loaded via XHR as Blobs.
 * However, you can set `loader.imageLoadType: "HTMLImageElement"` in the Game Configuration and instead, the Loader will load all images
 * via the Image tag instead.
 *
 * Note: The ability to load this type of file will only be available if the Image File type has been built into Phaser.
 * It is available in the default build but can be excluded from custom builds.
 *
 * @method Phaser.Loader.LoaderPlugin#image
 * @fires Phaser.Loader.LoaderPlugin#ADD
 * @since 3.0.0
 *
 * @param {(string|Phaser.Types.Loader.FileTypes.ImageFileConfig|Phaser.Types.Loader.FileTypes.ImageFileConfig[])} key - The key to use for this file, or a file configuration object, or array of them.
 * @param {string|string[]} [url] - The absolute or relative URL to load this file from. If undefined or `null` it will be set to `<key>.png`, i.e. if `key` was "alien" then the URL will be "alien.png".
 * @param {Phaser.Types.Loader.XHRSettingsObject} [xhrSettings] - An XHR Settings configuration object. Used in replacement of the Loaders default XHR Settings.
 *
 * @return {this} The Loader instance.
 */
FileTypesManager.register('image', function (key, url, xhrSettings)
{
    if (Array.isArray(key))
    {
        for (var i = 0; i < key.length; i++)
        {
            //  If it's an array it has to be an array of Objects, so we get everything out of the 'key' object
            this.addFile(new ImageFile(this, key[i]));
        }
    }
    else
    {
        this.addFile(new ImageFile(this, key, url, xhrSettings));
    }

    return this;
});

module.exports = ImageFile;


precision highp float;
uniform mat4 uProjectionMatrix;
attribute vec2 inPosition;

#define SHADER_NAME PHASER_MULTI_V2_FS
#define numTextures %count%

precision highp float;

uniform sampler2D uMainSampler[%count%];

varying vec2 outTexCoord;
varying float outTexId;
varying float outTintEffect;
varying vec4 outTint;

vec4 getSampler (int index, vec2 uv)
{
    for (int i = 0; i < numTextures; ++i)
    {
        if (i == index)
        {
            return texture2D(uMainSampler[i], uv);
        }
    }

    //  Return black
    return vec4(0);
}

void main ()
{
    vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);

    vec4 texture = getSampler(int(outTexId), outTexCoord);

    //  Multiply texture tint
    vec4 color = texture * texel;

    if (outTintEffect == 1.0)
    {
        //  Solid color + texture alpha
        color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);
    }
    else if (outTintEffect == 2.0)
    {
        //  Solid color, no texture
        color = texel;
    }

    gl_FragColor = color;
}


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * The Game Object Removed from Scene Event.
 *
 * This event is dispatched when a Game Object is removed from a Scene.
 *
 * Listen for it on a Game Object instance using `GameObject.on('removedfromscene', listener)`.
 *
 * @event Phaser.GameObjects.Events#REMOVED_FROM_SCENE
 * @type {string}
 * @since 3.50.0
 *
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that was removed from the Scene.
 * @param {Phaser.Scene} scene - The Scene from which the Game Object was removed.
 */
module.exports = 'removedfromscene';


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Tatar SNES USB Controller Gamepad Configuration.
 * USB Gamepad  (STANDARD GAMEPAD Vendor: 0079 Product: 0011)
 *
 * @name Phaser.Input.Gamepad.Configs.SNES_USB
 * @namespace
 * @since 3.0.0
 */
module.exports = {

    /**
     * D-Pad up
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.UP
     * @const
     * @type {number}
     * @since 3.0.0
     */
    UP: 12,

    /**
     * D-Pad down
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.DOWN
     * @const
     * @type {number}
     * @since 3.0.0
     */
    DOWN: 13,

    /**
     * D-Pad left
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.LEFT
     * @const
     * @type {number}
     * @since 3.0.0
     */
    LEFT: 14,

    /**
     * D-Pad right
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.RIGHT
     * @const
     * @type {number}
     * @since 3.0.0
     */
    RIGHT: 15,

    /**
     * Select button
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.SELECT
     * @const
     * @type {number}
     * @since 3.0.0
     */
    SELECT: 8,

    /**
     * Start button
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.START
     * @const
     * @type {number}
     * @since 3.0.0
     */
    START: 9,

    /**
     * B Button (Bottom)
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.B
     * @const
     * @type {number}
     * @since 3.0.0
     */
    B: 0,

    /**
     * A Button (Right)
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.A
     * @const
     * @type {number}
     * @since 3.0.0
     */
    A: 1,

    /**
     * Y Button (Left)
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.Y
     * @const
     * @type {number}
     * @since 3.0.0
     */
    Y: 2,

    /**
     * X Button (Top)
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.X
     * @const
     * @type {number}
     * @since 3.0.0
     */
    X: 3,

    /**
     * Left bumper
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.LEFT_SHOULDER
     * @const
     * @type {number}
     * @since 3.0.0
     */
    LEFT_SHOULDER: 4,

    /**
     * Right bumper
     *
     * @name Phaser.Input.Gamepad.Configs.SNES_USB.RIGHT_SHOULDER
     * @const
     * @type {number}
     * @since 3.0.0
     */
    RIGHT_SHOULDER: 5

};


/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2022 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Contains methods for changing the collision filter of a Matter Body. Should be used as a mixin and not called directly.
 *
 * @namespace Phaser.Physics.Matter.Components.Collision
 * @since 3.0.0
 */
var Collision = {

    /**
     * Sets the collision category of this Game Object's Matter Body. This number must be a power of two between 2^0 (= 1) and 2^31.
     * Two bodies with different collision groups (see {@link #setCollisionGroup}) will only collide if their collision
     * categories are included in their collision masks (see {@link #setCollidesWith}).
     *
     * @method Phaser.Physics.Matter.Components.Collision#setCollisionCategory
     * @since 3.0.0
     *
     * @param {number} value - Unique category bitfield.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setCollisionCategory: function (value)
    {
        this.body.collisionFilter.category = value;

        return this;
    },

    /**
     * Sets the collision group of this Game Object's Matter Body. If this is zero or two Matter Bodies have different values,
     * they will collide according to the usual rules (see {@link #setCollisionCategory} and {@link #setCollisionGroup}).
     * If two Matter Bodies have the same positive value, they will always collide; if they have the same negative value,
     * they will never collide.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setCollisionGroup
     * @since 3.0.0
     *
     * @param {number} value - Unique group index.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setCollisionGroup: function (value)
    {
        this.body.collisionFilter.group = value;

        return this;
    },

    /**
     * Sets the collision mask for this Game Object's Matter Body. Two Matter Bodies with different collision groups will only
     * collide if each one includes the other's category in its mask based on a bitwise AND, i.e. `(categoryA & maskB) !== 0`
     * and `(categoryB & maskA) !== 0` are both true.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setCollidesWith
     * @since 3.0.0
     *
     * @param {(number|number[])} categories - A unique category bitfield, or an array of them.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setCollidesWith: function (categories)
    {
        var flags = 0;

        if (!Array.isArray(categories))
        {
            flags = categories;
        }
        else
        {
            for (var i = 0; i < categories.length; i++)
            {
                flags |= categories[i];
            }
        }

        this.body.collisionFilter.mask = flags;

        return this;
    },

    /**
     * The callback is sent a `Phaser.Types.Physics.Matter.MatterCollisionData` object.
     *
     * This does not change the bodies collision category, group or filter. Those must be set in addition
     * to the callback.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setOnCollide
     * @since 3.22.0
     *
     * @param {function} callback - The callback to invoke when this body starts colliding with another.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setOnCollide: function (callback)
    {
        this.body.onCollideCallback = callback;

        return this;
    },

    /**
     * The callback is sent a `Phaser.Types.Physics.Matter.MatterCollisionData` object.
     *
     * This does not change the bodies collision category, group or filter. Those must be set in addition
     * to the callback.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setOnCollideEnd
     * @since 3.22.0
     *
     * @param {function} callback - The callback to invoke when this body stops colliding with another.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setOnCollideEnd: function (callback)
    {
        this.body.onCollideEndCallback = callback;

        return this;
    },

    /**
     * The callback is sent a `Phaser.Types.Physics.Matter.MatterCollisionData` object.
     *
     * This does not change the bodies collision category, group or filter. Those must be set in addition
     * to the callback.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setOnCollideActive
     * @since 3.22.0
     *
     * @param {function} callback - The callback to invoke for the duration of this body colliding with another.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setOnCollideActive: function (callback)
    {
        this.body.onCollideActiveCallback = callback;

        return this;
    },

    /**
     * The callback is sent a reference to the other body, along with a `Phaser.Types.Physics.Matter.MatterCollisionData` object.
     *
     * This does not change the bodies collision category, group or filter. Those must be set in addition
     * to the callback.
     *
     * @method Phaser.Physics.Matter.Components.Collision#setOnCollideWith
     * @since 3.22.0
     *
     * @param {(MatterJS.Body|MatterJS.Body[])} body - The body, or an array of bodies, to test for collisions with.
     * @param {function} callback - The callback to invoke when this body collides with the given body or bodies.
     *
     * @return {Phaser.GameObjects.GameObject} This Game Object.
     */
    setOnCollideWith: function (body, callback)
    {
        if (!Array.isArray(body))
        {
            body = [ body ];
        }

        for (var i = 0; i < body.length; i++)
        {
            var src = (body[i].hasOwnProperty('body')) ? body[i].body : body[i];

            this.body.setOnCollideWith(src, callback);
        }

        return this;
    }

};

module.exports = Collision;


obj.options = L.extend({}, obj.options, options);

getParamString: function (obj, existingUrl) {

keep keep keep keep keep replace keep keep

getParamString: function (obj, existingUrl, uppercase) {

/*
 * L.TileLayer.WMS is used for putting WMS tile layers on the map.
 */

L.TileLayer.WMS = L.TileLayer.extend({

	defaultWmsParams: {
		service: 'WMS',
		request: 'GetMap',
		version: '1.1.1',
		layers: '',
		styles: '',
		format: 'image/jpeg',
		transparent: false
	},

	initialize: function (url, options) { // (String, Object)

		this._url = url;

		var wmsParams = L.extend({}, this.defaultWmsParams),
		    tileSize = options.tileSize || this.options.tileSize;

		if (options.detectRetina && L.Browser.retina) {
			wmsParams.width = wmsParams.height = tileSize * 2;
		} else {
			wmsParams.width = wmsParams.height = tileSize;
		}

		for (var i in options) {
			// all keys that are not TileLayer options go to WMS params
			if (!this.options.hasOwnProperty(i)) {
				wmsParams[i] = options[i];
			}
		}

		this.wmsParams = wmsParams;

		L.setOptions(this, options);
	},

	onAdd: function (map) {

		var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs';
		this.wmsParams[projectionKey] = map.options.crs.code;

		L.TileLayer.prototype.onAdd.call(this, map);
	},

	getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String

		var map = this._map,
		    crs = map.options.crs,
		    tileSize = this.options.tileSize,

		    nwPoint = tilePoint.multiplyBy(tileSize),
		    sePoint = nwPoint.add([tileSize, tileSize]),

		    nw = crs.project(map.unproject(nwPoint, zoom)),
		    se = crs.project(map.unproject(sePoint, zoom)),

		    bbox = [nw.x, se.y, se.x, nw.y].join(','),

		    url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});

		return url + L.Util.getParamString(this.wmsParams, url) + '&bbox=' + bbox;
	},

	setParams: function (params, noRedraw) {

		L.extend(this.wmsParams, params);

		if (!noRedraw) {
			this.redraw();
		}

		return this;
	}
});

L.tileLayer.wms = function (url, options) {
	return new L.TileLayer.WMS(url, options);
};


/*
 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
 */

L.Map.mergeOptions({
	touchZoom: L.Browser.touch && !L.Browser.android23
});

L.Map.TouchZoom = L.Handler.extend({
	addHooks: function () {
		L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
	},

	removeHooks: function () {
		L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
	},

	_onTouchStart: function (e) {
		var map = this._map;

		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }

		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
		    p2 = map.mouseEventToLayerPoint(e.touches[1]),
		    viewCenter = map._getCenterLayerPoint();

		this._startCenter = p1.add(p2)._divideBy(2);
		this._startDist = p1.distanceTo(p2);

		this._moved = false;
		this._zooming = true;

		this._centerOffset = viewCenter.subtract(this._startCenter);

		if (map._panAnim) {
			map._panAnim.stop();
		}

		L.DomEvent
		    .on(document, 'touchmove', this._onTouchMove, this)
		    .on(document, 'touchend', this._onTouchEnd, this);

		L.DomEvent.preventDefault(e);
	},

	_onTouchMove: function (e) {
		var map = this._map;

		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }

		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
		    p2 = map.mouseEventToLayerPoint(e.touches[1]);

		this._scale = p1.distanceTo(p2) / this._startDist;
		this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);

		if (this._scale === 1) { return; }

		if (!this._moved) {
			L.DomUtil.addClass(map._mapPane, 'leaflet-touching');

			map
			    .fire('movestart')
			    .fire('zoomstart');

			this._moved = true;
		}

		L.Util.cancelAnimFrame(this._animRequest);
		this._animRequest = L.Util.requestAnimFrame(
		        this._updateOnMove, this, true, this._map._container);

		L.DomEvent.preventDefault(e);
	},

	_updateOnMove: function () {
		var map = this._map,
		    origin = this._getScaleOrigin(),
		    center = map.layerPointToLatLng(origin),
		    zoom = map.getScaleZoom(this._scale);

		map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta);
	},

	_onTouchEnd: function () {
		if (!this._moved || !this._zooming) {
			this._zooming = false;
			return;
		}

		var map = this._map;

		this._zooming = false;
		L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
		L.Util.cancelAnimFrame(this._animRequest);

		L.DomEvent
		    .off(document, 'touchmove', this._onTouchMove)
		    .off(document, 'touchend', this._onTouchEnd);

		var origin = this._getScaleOrigin(),
		    center = map.layerPointToLatLng(origin),

		    oldZoom = map.getZoom(),
		    floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
		    roundZoomDelta = (floatZoomDelta > 0 ?
		            Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),

		    zoom = map._limitZoom(oldZoom + roundZoomDelta),
		    scale = map.getZoomScale(zoom) / this._scale;

		map._animateZoom(center, zoom, origin, scale);
	},

	_getScaleOrigin: function () {
		var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
		return this._startCenter.add(centerOffset);
	}
});

L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);


<!DOCTYPE html>

    <title>Leaflet debug page</title>

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <link rel="stylesheet" href="../../dist/leaflet.css" />

    <link rel="stylesheet" href="../css/screen.css" />

        <button type="button" id="b1"> Add Layer</button>
        <button type="button" id="b2"> Remove Layer</button>


<!DOCTYPE html>

	<title>Leaflet debug page</title>

	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

	<link rel="stylesheet" href="../../dist/leaflet.css" />

	<link rel="stylesheet" href="../css/screen.css" />


keep keep replace keep keep keep keep keep

params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));

/*
 * L.Util contains various utility functions used throughout Leaflet code.
 */

L.Util = {
	extend: function (dest) { // (Object[, Object, ...]) ->
		var sources = Array.prototype.slice.call(arguments, 1),
		    i, j, len, src;

		for (j = 0, len = sources.length; j < len; j++) {
			src = sources[j] || {};
			for (i in src) {
				if (src.hasOwnProperty(i)) {
					dest[i] = src[i];
				}
			}
		}
		return dest;
	},

	bind: function (fn, obj) { // (Function, Object) -> Function
		var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
		return function () {
			return fn.apply(obj, args || arguments);
		};
	},

	stamp: (function () {
		var lastId = 0,
		    key = '_leaflet_id';
		return function (obj) {
			obj[key] = obj[key] || ++lastId;
			return obj[key];
		};
	}()),

	invokeEach: function (obj, method, context) {
		var i, args;

		if (typeof obj === 'object') {
			args = Array.prototype.slice.call(arguments, 3);

			for (i in obj) {
				method.apply(context, [i, obj[i]].concat(args));
			}
			return true;
		}

		return false;
	},

	limitExecByInterval: function (fn, time, context) {
		var lock, execOnUnlock;

		return function wrapperFn() {
			var args = arguments;

			if (lock) {
				execOnUnlock = true;
				return;
			}

			lock = true;

			setTimeout(function () {
				lock = false;

				if (execOnUnlock) {
					wrapperFn.apply(context, args);
					execOnUnlock = false;
				}
			}, time);

			fn.apply(context, args);
		};
	},

	falseFn: function () {
		return false;
	},

	formatNum: function (num, digits) {
		var pow = Math.pow(10, digits || 5);
		return Math.round(num * pow) / pow;
	},

	trim: function (str) {
		return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
	},

	splitWords: function (str) {
		return L.Util.trim(str).split(/\s+/);
	},

	setOptions: function (obj, options) {
		obj.options = L.extend({}, obj.options, options);
		return obj.options;
	},

	getParamString: function (obj, existingUrl) {
		var params = [];
		for (var i in obj) {
			params.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));
		}
		return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
	},

	template: function (str, data) {
		return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
			var value = data[key];
			if (value === undefined) {
				throw new Error('No value provided for variable ' + str);
			} else if (typeof value === 'function') {
				value = value(data);
			}
			return value;
		});
	},

	isArray: function (obj) {
		return (Object.prototype.toString.call(obj) === '[object Array]');
	},

	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
};

(function () {

	// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/

	function getPrefixed(name) {
		var i, fn,
		    prefixes = ['webkit', 'moz', 'o', 'ms'];

		for (i = 0; i < prefixes.length && !fn; i++) {
			fn = window[prefixes[i] + name];
		}

		return fn;
	}

	var lastTime = 0;

	function timeoutDefer(fn) {
		var time = +new Date(),
		    timeToCall = Math.max(0, 16 - (time - lastTime));

		lastTime = time + timeToCall;
		return window.setTimeout(fn, timeToCall);
	}

	var requestFn = window.requestAnimationFrame ||
	        getPrefixed('RequestAnimationFrame') || timeoutDefer;

	var cancelFn = window.cancelAnimationFrame ||
	        getPrefixed('CancelAnimationFrame') ||
	        getPrefixed('CancelRequestAnimationFrame') ||
	        function (id) { window.clearTimeout(id); };

	L.Util.requestAnimFrame = function (fn, context, immediate, element) {
		fn = L.bind(fn, context);

		if (immediate && requestFn === timeoutDefer) {
			fn();
		} else {
			return requestFn.call(window, fn, element);
		}
	};

	L.Util.cancelAnimFrame = function (id) {
		if (id) {
			cancelFn.call(window, id);
		}
	};

}());

// shortcuts for most used utility functions
L.extend = L.Util.extend;
L.bind = L.Util.bind;
L.stamp = L.Util.stamp;
L.setOptions = L.Util.setOptions;


<!DOCTYPE html>

	<title>Leaflet Build Helper</title>

Leaflet Build Helper

			Select All |
			Deselect All

Building using Node and UglifyJS

Download and install Node

Run this in the command line:

npm install -g jake
npm install jshint
npm install uglify-js

Run this command inside the Leaflet directory:
<input type="text" id="command2" />

Building using Closure Compiler

Download Closure Compiler, extract it into closure-compiler directory

Run this command in the root Leaflet directory:
<input type="text" id="command" />


<!DOCTYPE html>

	<title>Leaflet debug page</title>

	<link rel="stylesheet" href="../../dist/leaflet.css" />

	<link rel="stylesheet" href="../css/screen.css" />

	<button onclick="geojsonLayerBounds();">Show GeoJSON layer bounds</button>
	<button onclick="featureGroupBounds();">Show feature group bounds</button>


<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="744.094" height="1052.362"><defs><linearGradient id="b"><stop offset="0" stop-color="#2e6c97"/><stop offset="1" stop-color="#3883b7"/></linearGradient><linearGradient id="a"><stop offset="0" stop-color="#126fc6"/><stop offset="1" stop-color="#4c9cd1"/></linearGradient><linearGradient xlink:href="#a" x1="351.138" y1="551.589" x2="351.138" y2="512.928" gradientUnits="userSpaceOnUse" gradientTransform="translate(-2.715)"/><linearGradient xlink:href="#a" x1="318.571" y1="550.052" x2="318.571" y2="512.422" gradientUnits="userSpaceOnUse" gradientTransform="translate(94.732 2.054)"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 731.268 2.054)" x1="318.571" y1="550.052" x2="318.571" y2="512.422"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(94.232 2.054)" x1="318.571" y1="550.052" x2="318.571" y2="512.422"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.58 -.437)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(63 -.438)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/><linearGradient xlink:href="#a" id="c" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.846 -.287)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/><linearGradient xlink:href="#b" id="d" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.734 -.288)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/><linearGradient xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(-28.846 -.287)" x1="445.301" y1="541.286" x2="445.301" y2="503.72"/><linearGradient xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(62.734 -.288)" x1="351.748" y1="522.774" x2="351.748" y2="503.721"/></defs><g><image y="502.237" x="369.375" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAABHNCSVQICAgIfAhkiAAABgRJREFU WIWdl2uIXGcZx3/nOjNnZueWZlPUtgiKF2gRugWD9UswJfWD+MG7WC1SQcGP6ZdAkvpp28LK0ohQ qASE0tKgELtaGiwEi0EWKXFtbKRmQ2wys5nZy8ycM+f+Pn6YvZ2dmd2J7/DAYc77vL/3/1zel6PN PPcn7mF8CqhsPl8H3MXTJw500mae++N+7+vAj4ATwHHbstANDYAoSlBKNYGLwKuLp5+8PB5ydiTE AE4Cp+q1cqlYLFB08kOTkiTF9Xy6XY++H7wFPLt45smlEZCFUbt/rVYpH6/VylimSZgqvChFJDvR NnWmbAOAjU6PldaqDzy1eOarF3bP00WEXVYXkb/Wq5Xjh++rk4hO24vpBSlKgUjWwljR9mJW+zGV 8hSf+NiRQqnovDFzduGnGcieEL1Rr1U/Uz9UY91P6IUpSjjQUgUtL8bO5anVqwAvzZxdOLYD2dna yUp56litXmO9nxClMnJBU9eQMbDVfoJl2Rw5Mm0g8trMmTfrg3AhCFIX5FS5UqUfKqJEUIptyxs6 tYJFrWBha0J187mSNzPzlIJWL8ZxilSq1cOCnNxUAgjfK09VSoZh4oZpJu51x8I2NNZWV7m5vIze X+Pm8g1WVlYIg4C6YwHZXHlhSqVSBeEnj57+g2Fulsy3nWKJMFGoXRVUyZuEQUiz8RFPf/nTPPX4 FynlB4u+vXSbFxaWCPNlapUqbS/e9vMiRcGyyOdy9SAIjuqClAQ5att5Ov00I93QNDqdDU597RF+ 9pXPbgMAnnj44/zmmcfxe+ukiSJn6EOhyxUcBDmhI/KgZVqGEkh3JdEydMIoYrqg+PqjD+3tJUSE B+pFvvHYQ3S7HfKWkSmCfqwwTRtEPq+LSF3T9aFKyZk6QeDz8AO1kQAApRRfeLCG73tDFZcoAU1H RComIpEoNUia2llIpYBAnKixABEhiFM0TRvy10RDDTrY10Fup0k8tBMvSrHzRRb/c5c4HQZtQf7y wR1y+RLJnr4yNI00SQC5rYtIM0liN1UKTdO2JwWJoOkmrspz7u33h1QAXPl3g0sfrJMvVugG2dNB 1zXiOEJEbhn3f+mbAjxmWIXPlQoFvFBlar7olHjvxl2u3rjD/VWHQ6Ucy3c7XPjbDZ5/cwmnPI1h 5ugGO/2lAQXLoLfWQKXJz7VHnn0d4JidL/65Ov1JWm6cCY1j6xRtg8DbwHfXSCIfw7TJORUKpTq6 YQ75HC5ZBN4G3dWPLl19/ltPbDXjO5HvXgl99+ghp0jbS3YaK1T4kXCoWCXnVDOL9WOF188CNAZq /N4aiPwCwITtFn/R763+zrJLqGyeUQgr3exi48Z0ySL0OsSh+87VF77zLmTvk4thf+PDOA4o582J jviRBvTdVURkdgu8+6hPEZnze21MQ/u/ADXHJPJdYr/793+8+N1L25A9t935oNdupWlCwRo+BQ4y TdPw3TYizO4OoU6W4iPyctBt4VgGopjYanmTJOgTe+vXEfl9BrLnjkdE5oNOw1dKYZuTq9F1jcBt ISKzS3PfT7NKNm+tXdYC+W3otpnKGRMBynmDNA6IvfYtkFf3VtzecG3ZXLhxKxUR9K3Dbx+zDZ3I bYHI3D9/+YNoCDIiXIjIdRFZiLw16gUDpWSsOZZOmkREvWZLRF4Z1TvjlIDIXOzdRbF/qAq2TuSu gMi59+d/6I6EjFGCiFxOg+6VNOhxX9EcuQ/H0hGVEnfvuCIyPwowLvG7bT72VkAb3ZxOziDuNQH5 9bWXnu6Mh+yf1Quq3/5QYp9Kwci8ypk6KCHp/jdCZG4c4KBwISKpiMwnbhPb0IfKNnYbiMj5a+d+ 3NwXckC4ADmfeo2WpDFFeyDc0jVEhLR7MwWZHb/8tpL9e0AEV4SXE6+JYw+as+qYJN4KIrz+r189 s3wg5MBOG9g51b0ViRJy5qaL2wA5WMWk4QKkCXJe9ZtU8gbKbyGJtwAy9FU1RskkDECYF68xUOE1 QJjduVQPgGx+OkzyuyaJd1GCFhJ1LgvyrkxImTQn20eNeHcGudj6byLIvY3LRJ1XgLfuxel/1MGY 0YvhJSIAAAAASUVORK5CYII=" height="41" width="25"/><rect y="507.575" x="410.279" height="14.5" width="12.625" fill="#fff"/><path d="m416.544 503.612c-6.573 0-12.044 5.691-12.044 11.866 0 2.778 1.564 6.308 2.694 8.746l9.306 17.872 9.262-17.872c1.13-2.438 2.738-5.791 2.738-8.746 0-6.175-5.383-11.866-11.956-11.866zm0 7.155c2.584.017 4.679 2.122 4.679 4.71s-2.095 4.663-4.679 4.679c-2.584-.017-4.679-2.09-4.679-4.679 0-2.588 2.095-4.693 4.679-4.71z" fill="url(#c)" stroke="url(#d)" stroke-width="1.1" stroke-linecap="round"/><text style="line-height:125%" x="403.75" y="489.362" font-size="4" letter-spacing="0" word-spacing="0" font-family="Sans"><tspan x="403.75" y="489.362">Use this one.</tspan><tspan x="403.75" y="494.362">Select it and the area around with box select.</tspan><tspan x="403.75" y="499.362">should be exactly 25x41 at 90dpi</tspan></text><text style="line-height:125%" x="408.708" y="563.751" font-size="40" letter-spacing="0" word-spacing="0" font-family="Sans"><tspan x="408.708" y="563.751" font-size="4">How to do the inset border:</tspan><tspan x="408.708" y="568.751" font-size="4">Delete the existing inset border. Select the background, duplicate.</tspan><tspan x="408.708" y="573.751" font-size="4">Path, Dynamic offset.</tspan><tspan x="408.708" y="578.751" font-size="4">Fill: None, Stroke Paint: RGBA #ffffff1f</tspan><tspan x="408.708" y="583.751" font-size="4">Zoom down to the top and grab the diamond, drag it down and fiddle it untill it looks in line</tspan><tspan x="408.708" y="588.751" font-size="4">with the main color layers border.</tspan></text><path d="m416.531 504.719c-5.944 0-10.938 5.219-10.938 10.75 0 2.359 1.443 5.832 2.563 8.25l.031.031 8.313 15.969 8.25-15.969.031-.031c1.135-2.448 2.625-5.706 2.625-8.25 0-5.538-4.931-10.75-10.875-10.75zm0 4.969c3.168.021 5.781 2.601 5.781 5.781 0 3.18-2.613 5.761-5.781 5.781-3.168-.02-5.75-2.61-5.75-5.781 0-3.172 2.582-5.761 5.75-5.781z" stroke="#fff" stroke-width="1.1" stroke-linecap="round" stroke-opacity=".122" fill="none"/></g></svg>


bbox = [nw.x, se.y, se.x, nw.y].join(','),

url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});

return url + L.Util.getParamString(this.wmsParams, url) + '&bbox=' + bbox;

setParams: function (params, noRedraw) {

keep keep keep keep replace keep keep keep

return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;


/*
 * L.TileLayer.Canvas is a class that you can use as a base for creating
 * dynamically drawn Canvas-based tile layers.
 */

L.TileLayer.Canvas = L.TileLayer.extend({
	options: {
		async: false
	},

	initialize: function (options) {
		L.setOptions(this, options);
	},

	redraw: function () {
		for (var i in this._tiles) {
			this._redrawTile(this._tiles[i]);
		}
		return this;
	},

	_redrawTile: function (tile) {
		this.drawTile(tile, tile._tilePoint, this._map._zoom);
	},

	_createTileProto: function () {
		var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
		proto.width = proto.height = this.options.tileSize;
	},

	_createTile: function () {
		var tile = this._canvasProto.cloneNode(false);
		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
		return tile;
	},

	_loadTile: function (tile, tilePoint) {
		tile._layer = this;
		tile._tilePoint = tilePoint;

		this._redrawTile(tile);

		if (!this.options.async) {
			this.tileDrawn(tile);
		}
	},

	drawTile: function (/*tile, tilePoint*/) {
		// override with rendering code
	},

	tileDrawn: function (tile) {
		this._tileOnLoad.call(tile);
	}
});

L.tileLayer.canvas = function (options) {
	return new L.TileLayer.Canvas(options);
};


describe('CircleMarker', function() {
	describe("#_radius", function() {
		var map;
		beforeEach(function() {
			map = L.map(document.createElement('div'));
			map.setView([0, 0], 1);
		});
		describe("when a CircleMarker is added to the map ", function() {
			describe("with a radius set as an option", function() {
				it("takes that radius", function() {
					var marker = L.circleMarker([0, 0], { radius: 20 }).addTo(map);

					expect(marker._radius).to.be(20);
				});
			});

			describe("and radius is set before adding it", function () {
				it("takes that radius", function () {
					var marker = L.circleMarker([0, 0], { radius: 20 });
					marker.setRadius(15);
					marker.addTo(map);
					expect(marker._radius).to.be(15);
				});
			});

			describe("and radius is set after adding it", function () {
				it("takes that radius", function () {
					var marker = L.circleMarker([0, 0], { radius: 20 });
					marker.addTo(map);
					marker.setRadius(15);
					expect(marker._radius).to.be(15);
				});
			});

			describe("and setStyle is used to change the radius after adding", function () {
				it("takes the given radius", function() {
					var marker = L.circleMarker([0, 0], { radius: 20 });
					marker.addTo(map);
					marker.setStyle({ radius: 15 });
					expect(marker._radius).to.be(15);
				});
			});
			describe("and setStyle is used to change the radius before adding", function () {
				it("takes the given radius", function () {
					var marker = L.circleMarker([0, 0], { radius: 20 });
					marker.setStyle({ radius: 15 });
					marker.addTo(map);
					expect(marker._radius).to.be(15);
				});
			});
		});
	});
});


/*
 * L.PolyUtil contains utility functions for polygons (clipping, etc.).
 */

/*jshint bitwise:false */ // allow bitwise operations here

L.PolyUtil = {};

/*
 * Sutherland-Hodgeman polygon clipping algorithm.
 * Used to avoid rendering parts of a polygon that are not currently visible.
 */
L.PolyUtil.clipPolygon = function (points, bounds) {
	var clippedPoints,
	    edges = [1, 4, 2, 8],
	    i, j, k,
	    a, b,
	    len, edge, p,
	    lu = L.LineUtil;

	for (i = 0, len = points.length; i < len; i++) {
		points[i]._code = lu._getBitCode(points[i], bounds);
	}

	// for each edge (left, bottom, right, top)
	for (k = 0; k < 4; k++) {
		edge = edges[k];
		clippedPoints = [];

		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
			a = points[i];
			b = points[j];

			// if a is inside the clip window
			if (!(a._code & edge)) {
				// if b is outside the clip window (a->b goes out of screen)
				if (b._code & edge) {
					p = lu._getEdgeIntersection(b, a, edge, bounds);
					p._code = lu._getBitCode(p, bounds);
					clippedPoints.push(p);
				}
				clippedPoints.push(a);

			// else if b is inside the clip window (a->b enters the screen)
			} else if (!(b._code & edge)) {
				p = lu._getEdgeIntersection(b, a, edge, bounds);
				p._code = lu._getBitCode(p, bounds);
				clippedPoints.push(p);
			}
		}
		points = clippedPoints;
	}

	return points;
};


import React, {
  FC,
  Fragment,
  ReactNode,
  useEffect,
  useId,
  useRef,
  useState,
} from 'react';
import { OverlayContainer, useDrop } from 'react-aria';
import { useFetcher } from 'react-router-dom';
import { usePrevious } from 'react-use';
import styled from 'styled-components';

import {
  ImportResourcesActionResult,
  ScanForResourcesActionResult,
} from '../../routes/import';
import { Modal, ModalHandle, ModalProps } from '../base/modal';
import { ModalHeader } from '../base/modal-header';
import { Button } from '../themed-button';
import { hideAllModals } from '.';

const Pill = styled.div({
  display: 'flex',
  alignItems: 'center',
  gap: 'var(--padding-xs)',
  padding: 'var(--padding-sm)',
  borderRadius: 'var(--radius-md)',
  fontSize: 'var(--font-size-xs)',
});

const RadioGroup = styled.div({
  display: 'flex',
  padding: 'var(--padding-xs)',
  border: '1px solid var(--hl-md)',
  borderRadius: 'var(--radius-md)',
  backgroundColor: 'var(--hl-xs)',
  'input[type="radio"]:checked ~ label': {
    backgroundColor: 'var(--color-bg)',
    boxShadow: '0 0 5px 1px var(--hl-xs)',
  },
});

const RadioLabel = styled.label({
  padding: 'var(--padding-sm)',
  borderRadius: 'var(--radius-md)',
  display: 'flex',
  alignItems: 'center',
  gap: 'var(--padding-sm)',
});

const RadioInput = styled.input({
  position: 'absolute',
  width: '1px',
  height: '1px',
  padding: '0',
  margin: '-1px',
  overflow: 'hidden',
  clip: 'rect(0,0,0,0)',
  whiteSpace: 'nowrap',
  borderWidth: '0',
});

const Radio: FC<{
  name: string;
  value: string;
  children: ReactNode;
  checked?: boolean;
  defaultChecked?: boolean;
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
}> = ({ name, value, onChange, children, checked, defaultChecked }) => {
  const id = useId();
  return (

      <RadioInput
        id={id}
        type="radio"
        name={name}
        checked={checked}
        value={value}
        defaultChecked={defaultChecked}
        onChange={onChange}
      />
      <RadioLabel htmlFor={id}>{children}</RadioLabel>

  );
};

const Fieldset = styled.fieldset({
  display: 'flex',
  flexDirection: 'column',
  gap: 'var(--padding-md)',
  legend: {
    marginBottom: 'var(--padding-xs)',
  },
});

const FileInput = styled.input({
  display: 'none',
});

const FileInputLabel = styled.label({
  padding: 'var(--padding-sm)',
  borderRadius: 'var(--radius-md)',
  display: 'flex',
  alignItems: 'center',
  gap: 'var(--padding-sm)',
  border: '1px solid var(--hl-md)',
  backgroundColor: 'var(--hl-xs)',
  flexWrap: 'wrap',
});

const FileView = styled.div({
  backgroundColor: 'var(--color-bg)',
  borderRadius: 'var(--radius-md)',
  textOverflow: 'ellipsis',
  overflow: 'hidden',
  whiteSpace: 'nowrap',
  display: 'flex',
  flexDirection: 'column',
  alignItems: 'center',
  justifyContent: 'center',
  padding: 'var(--padding-md)',
  gap: 'var(--padding-sm)',
  width: '100%',
});

const FileField: FC = () => {
  const id = useId();
  const dropRef = useRef<HTMLLabelElement>(null);
  const [selectedFile, setSelectedFile] = useState<File | undefined>();
  const { isDropTarget, dropProps } = useDrop({
    ref: dropRef,
    onDrop: async event => {
      const file =
        event.items[0].kind === 'file'
          ? await event.items[0].getFile()
          : undefined;

      if (file) {
        setSelectedFile(file);
      }
    },
  });
  return (

      <FileInput
        onChange={e => setSelectedFile(e.currentTarget.files?.[0])}
        accept={[
          '',
          'sh',
          'txt',
          'json',
          'har',
          'curl',
          'bash',
          'shell',
          'yaml',
          'yml',
          'wsdl',
        ].join(',')}
        id={id}
        type="file"
      />
      <FileInputLabel
        {...dropProps}
        style={{
          border: isDropTarget ? '1px solid var(--color-surprise)' : undefined,
        }}
        htmlFor={id}
      >
        {!selectedFile && (

              Drag and Drop or{' '}

                Choose a File
              {' '}
              to import

        )}
        {selectedFile && (
          <Fragment key={selectedFile.name}>
            <FileView key={selectedFile.path}>

              {selectedFile.name}
            </FileView>
            <input type="hidden" name="filePath" value={selectedFile.path} />
          </Fragment>
        )}
      </FileInputLabel>

  );
};

const InsomniaIcon = (props: React.SVGProps<SVGSVGElement>) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={18}
      height={18}
      viewBox="0 0 32 32"
      fill="none"
      {...props}
    >
      <path
        d="M16 31.186c8.387 0 15.186-6.799 15.186-15.186S24.387.814 16 .814.814 7.613.814 16 7.613 31.186 16 31.186z"
        fill="var(--color-bg)"
      />
      <path
        d="M16 0C7.163 0 0 7.163 0 16s7.163 16 16 16 16-7.163 16-16S24.837 0 16 0zm0 1.627c7.938 0 14.373 6.435 14.373 14.373S23.938 30.373 16 30.373 1.627 23.938 1.627 16 8.062 1.627 16 1.627z"
        fill="var(--color-font)"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M16.18 4.61c6.291 0 11.39 5.1 11.39 11.39 0 6.29-5.099 11.39-11.39 11.39-6.29 0-11.39-5.1-11.39-11.39 0-1.537.305-3.004.858-4.342a4.43 4.43 0 106.192-6.192 11.357 11.357 0 014.34-.856z"
        fill="var(--color-font)"
      />
      <defs>
        <linearGradient
          id="paint0_linear"
          x1={16.1807}
          y1={27.3898}
          x2={16.1807}
          y2={4.61017}
          gradientUnits="userSpaceOnUse"
        >
          <stop stopColor="#7400E1" />
          <stop offset={1} stopColor="#4000BF" />
        </linearGradient>
      </defs>
    </svg>
  );
};

const PostmanIcon = (props: React.SVGProps<SVGSVGElement>) => {
  return (
    <svg
      width={18}
      height={18}
      viewBox="0 0 32 32"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      {...props}
    >
      <path
        d="M18.038.13a16 16 0 10-4.076 31.74A16 16 0 0018.038.13z"
        fill="var(--color-font)"
      />
      <path
        d="M11.567 17.011a.06.06 0 00.07.032l2.56-.552-1.076-1.091-1.534 1.534a.06.06 0 00-.02.077zM23.555 6.02a2.386 2.386 0 101.005 4.55l-1.623-1.623a.2.2 0 010-.283l2.12-2.118a2.386 2.386 0 00-1.502-.527z"
        fill="var(--color-bg)"
      />
      <path
        d="M25.348 6.82L23.361 8.8l1.558 1.558a2.4 2.4 0 00.429-3.538zM21.372 10.474h-.035a.621.621 0 00-.123.01h-.015a.938.938 0 00-.13.04l-.034.015a.627.627 0 00-.093.048l-.035.023a.833.833 0 00-.11.09l-5.892 5.894.73.73 6.24-5.478a.727.727 0 00.096-.102l.027-.035a.87.87 0 00.11-.234c0-.019.011-.038.016-.057a.934.934 0 00.016-.12v-.053-.086c0-.03 0-.039-.008-.058a.778.778 0 00-.61-.613h-.03a.835.835 0 00-.12-.014zM13.396 15.117l1.21 1.203 5.909-5.909c.192-.188.442-.305.71-.331-1.045-.8-2.184-.59-7.829 5.037zM22.207 12.077l-.072.07-6.24 5.475 1.061 1.06c2.63-2.488 4.965-4.858 5.252-6.605zM6.643 24.904a.058.058 0 00.051.041l2.72.188-1.525-1.525-1.233 1.232a.061.061 0 00-.013.064zM8.174 23.325l1.608 1.608a.122.122 0 00.152.02.12.12 0 00.062-.139l-.27-1.155a.346.346 0 01.177-.386c2.82-1.412 5.093-2.867 6.762-4.32l-1.12-1.12-2.4.517-4.971 4.975zM15.201 17.494l-.601-.601-.832.83a.04.04 0 000 .051.038.038 0 00.046.021l1.387-.3z"
        fill="var(--color-bg)"
      />
      <path
        d="M25.404 8.11a.185.185 0 10-.33.16.557.557 0 01-.07.602.185.185 0 00.285.237.926.926 0 00.115-.998z"
        fill="var(--color-font)"
      />
    </svg>
  );
};

const SwaggerIcon = (props: React.SVGProps<SVGSVGElement>) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="18px"
      height="18px"
      viewBox="0 0 256 256"
      {...props}
    >
      <path
        fill="var(--color-font)"
        d="M127.999 249.895c-67.215 0-121.9-54.68-121.9-121.896C6.1 60.782 60.785 6.102 128 6.102c67.214 0 121.899 54.685 121.899 121.9 0 67.214-54.685 121.893-121.9 121.893z"
      />
      <path
        fill="var(--color-bg)"
        d="M127.999 12.202c63.954 0 115.797 51.842 115.797 115.797 0 63.952-51.843 115.797-115.797 115.797-63.952 0-115.797-51.845-115.797-115.797S64.047 12.202 127.999 12.202m0-12.202C57.419 0 0 57.42 0 127.999s57.42 127.998 127.999 127.998S256 198.577 256 128C256 57.419 198.578 0 127.999 0z"
      />
      <path
        fill="var(--color-bg)"
        d="M80.598 86.619c-.394 4.38.146 8.909-.146 13.338-.345 4.431-.887 8.811-1.773 13.192-1.23 6.25-5.12 10.976-10.482 14.914 10.436 6.793 11.616 17.324 12.304 28.006.345 5.76.197 11.567.788 17.276.443 4.429 2.165 5.562 6.745 5.708 1.87.048 3.786 0 5.956 0v13.683c-13.535 2.313-24.708-1.525-27.467-12.992-.887-4.184-1.478-8.467-1.673-12.798-.297-4.578.195-9.155-.148-13.732-.985-12.553-2.61-16.785-14.618-17.376v-15.602a23.714 23.714 0 012.608-.443c6.596-.345 9.4-2.364 10.828-8.86.69-3.641 1.084-7.333 1.23-11.074.494-7.136.297-14.42 1.525-21.507C67.997 68.163 74.3 63.24 84.785 62.65c2.952-.149 5.955 0 9.35 0v13.98c-1.427.1-2.658.294-3.937.294-8.515-.297-8.96 2.607-9.6 9.695zm16.39 32.386h-.196c-4.923-.245-9.155 3.593-9.403 8.515-.246 4.972 3.592 9.206 8.515 9.45h.59c4.875.296 9.056-3.447 9.352-8.319v-.491c.1-4.971-3.886-9.055-8.857-9.155zm30.862 0c-4.774-.148-8.763 3.593-8.909 8.318 0 .297 0 .543.051.837 0 5.365 3.641 8.812 9.155 8.812 5.414 0 8.812-3.544 8.812-9.106-.051-5.366-3.646-8.91-9.109-8.86zm31.602 0c-5.02-.1-9.206 3.89-9.352 8.91a9.03 9.03 0 009.055 9.054h.1c4.528.788 9.106-3.592 9.402-8.858.243-4.874-4.186-9.106-9.205-9.106zm43.363.737c-5.711-.245-8.567-2.164-9.992-7.581a54.874 54.874 0 01-1.624-10.582c-.395-6.596-.346-13.241-.789-19.837-1.033-15.651-12.352-21.114-28.794-18.41V76.92c2.607 0 4.626 0 6.645.049 3.495.048 6.153 1.379 6.496 5.268.345 3.543.345 7.136.69 10.73.692 7.139 1.083 14.372 2.314 21.41 1.085 5.809 5.07 10.14 10.04 13.684-8.71 5.857-11.27 14.223-11.714 23.626-.245 6.448-.394 12.944-.736 19.443-.297 5.905-2.362 7.824-8.318 7.972-1.674.05-3.298.198-5.169.297v13.93c3.495 0 6.694.196 9.892 0 9.942-.592 15.947-5.415 17.918-15.063a125.582 125.582 0 001.476-16.045c.343-4.923.297-9.894.788-14.766.737-7.63 4.232-10.78 11.862-11.27.739-.1 1.427-.246 2.118-.492v-15.604c-1.282-.149-2.17-.295-3.103-.346z"
      />
    </svg>
  );
};

const OpenAPIIcon = (props: React.SVGProps<SVGSVGElement>) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={17}
      height={17}
      viewBox="0 0 512 512"
      {...props}
    >
      <path
        fill="currentColor"
        d="M.204 294.376l135.073-.002a91.735 91.735 0 008.452 30.17L27.94 394.298C12.75 368.103 3.166 334.783.204 294.376zm131.802 196.803l51.407-124.841a91.475 91.475 0 01-14.938-9.955l-95.47 95.472c19.49 17.557 39.562 30.71 59.001 39.324zm-65.604-45.647l95.284-95.286c-4.933-4.963-9.482-10.85-13.667-17.616L32.512 402.213c10.491 17.069 22.148 31.847 33.89 43.319zm313.997 6.327l-95.53-95.525a91.813 91.813 0 01-3.184 2.466l69.71 115.72c9.73-6.055 19.39-13.823 29.004-22.66zm-36.74 27.532l-69.565-115.479c-25.964 14.5-53.318 17.553-82.406 6.337l-51.301 124.583c70.2 28.467 142.961 20.313 203.272-15.441zM171.713 211.67L102.005 95.95c-10.164 6.523-19.819 14.106-29.006 22.66l95.527 95.527a135.39 135.39 0 013.187-2.467zM0 285.236l134.762-.002c.291-23.838 8.71-45.682 26.928-65.006l-95.287-95.287C22.416 171.553.512 225.056 0 285.236zm226.694-91.938L226.7 58.54c-44.728.274-84.005 12.326-116.96 32.539l69.563 115.48c13.077-7.677 28.664-12.98 47.39-13.26zm186.404-37.118l-99.449 99.452a93.73 93.73 0 014.453 20.46h135.09c-.883-40.98-14.404-80.939-40.094-119.912zm40.28 129.052H318.631c-.468 25.24-9.9 48.244-26.924 65.014l95.29 95.286c43.82-43.123 65.122-96.948 66.381-160.3zM235.84 58.74l-.006 135.087c7.082.802 13.883 2.342 20.466 4.455l99.415-99.415c-35.132-24.575-76.54-38.362-119.875-40.127zM430.95 2.597c-39.165 11.457-55.533 55.183-38.782 88.672L254.808 228.63c-32.407-16.157-74.902-1.57-87.795 36.034-15.716 45.84 24.243 91.802 71.754 82.533 42.01-8.196 62.31-54.188 44.466-90.01L420.722 119.7c34.66 17.226 79.533-1.065 89.396-41.654 11.43-47.037-32.658-89.054-79.168-75.449z"
      />
    </svg>
  );
};

const CurlIcon = (props: React.SVGProps<SVGSVGElement>) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width={18}
      height={18}
      viewBox="0 0 123.184 102.926"
      {...props}
    >
      <defs>
        <symbol overflow="visible">
          <path
            d="M73.36 0v-84.172H13.233V0zm-9.626-78.156L43.297-47.5 22.844-78.156zm3.61 66.734L46.89-42.094 67.344-72.75zm-44.5 5.406l20.453-30.656L63.734-6.016zm-3.61-66.734l20.453 30.656-20.453 30.672zm0 0"
            stroke="none"
          />
        </symbol>
        <symbol overflow="visible">
          <path
            d="M15.031-26.813c0 7.094 1.328 10.579 6.86 15.141l8.53 7.11C35.829-.125 38.595 0 45.329 0H63.61c3.844 0 7.938-.844 7.938-5.766 0-5.062-4.563-5.78-7.938-5.78H43.297c-2.297 0-4.578-1.204-6.625-2.892l-6.969-5.89c-1.812-1.438-3.125-2.64-3.125-4.922v-14.313c0-2.28 1.313-3.484 3.125-4.937l6.969-5.89c2.047-1.672 4.328-2.876 6.625-2.876h20.312c3.375 0 7.938-.734 7.938-5.78 0-4.923-4.094-5.767-7.938-5.767H45.33c-6.735 0-9.5.126-14.907 4.563l-8.531 7.094c-5.532 4.578-6.86 8.062-6.86 15.156zm0 0"
            stroke="none"
          />
        </symbol>
        <symbol overflow="visible">
          <path
            d="M71.547-56.875c0-3.969-.844-7.938-5.766-7.938-4.937 0-5.781 3.97-5.781 7.938v33.547L43.047-12.625c-1.078.719-2.281 1.078-3.61 1.078h-3.843c-2.766 0-5.766-.719-6.735-2.406-.968-1.672-1.078-4.813-1.203-7.453l-1.203-35.469c-.125-3.734-.719-7.938-5.656-7.938-5.281 0-5.89 4.454-5.766 8.657l1.203 36.312c.235 6.25.125 11.188 5.297 15.64C25.375-.968 28.984 0 32.234 0h8.532c3.125 0 6.25-1.438 9.984-3.844l9.375-6.14v1.921c0 3.97.844 8.063 5.766 8.063 6.14 0 5.656-5.89 5.656-8.531zm0 0"
            stroke="none"
          />
        </symbol>
        <symbol overflow="visible">
          <path
            d="M15.031-7.938c0 3.97.844 7.938 5.766 7.938 4.937 0 5.781-3.969 5.781-7.938v-29.218L42.688-50.5c2.046-1.688 3.609-2.766 5.765-2.766h3.375c2.766 0 5.531 0 6.969 2.047C60-49.547 60-48.094 60-46.172c0 3.719 1.688 7.094 5.89 7.094 4.938 0 5.657-4.094 5.657-8.063 0-5.28-1.328-9.25-5.172-13.109-3.125-3.125-6.844-4.563-11.297-4.563h-9.266c-3.609 0-6.968 2.282-10.218 4.922l-9.016 7.47v-4.704c0-3.844-1.203-7.688-5.781-7.688-5.281 0-5.766 4.329-5.766 8.532zm0 0"
            stroke="none"
          />
        </symbol>
        <symbol overflow="visible">
          <path
            d="M49.297-80.563c0-6.859-1.922-8.062-8.531-8.062H28.625c-3.86 0-7.938.844-7.938 5.766 0 5.062 4.563 5.78 7.938 5.78h9.14v65.532h-9.14c-3.86 0-7.938.844-7.938 5.781C20.688-.719 25.25 0 28.625 0h29.328c3.375 0 7.938-.719 7.938-5.766 0-4.937-4.079-5.78-7.938-5.78h-8.656zm0 0"
            stroke="none"
          />
        </symbol>
      </defs>
      <path
        d="M114.102 14.043a4.96 4.96 0 01-4.961-4.961 4.958 4.958 0 014.96-4.96 4.96 4.96 0 110 9.921M64.833 98.805a4.96 4.96 0 01-4.96-4.961 4.958 4.958 0 014.96-4.961 4.957 4.957 0 014.957 4.96 4.96 4.96 0 01-4.957 4.962M114.102 0a9.082 9.082 0 00-9.082 9.082c0 1.07.27 2.066.609 3.02L63.023 85.125c-4.117.863-7.273 4.344-7.273 8.719a9.082 9.082 0 009.082 9.082c5.012 0 9.078-4.067 9.078-9.082 0-1.008-.27-1.93-.57-2.836l42.82-73.262c3.992-.957 7.024-4.379 7.024-8.664A9.082 9.082 0 00114.102 0"
        fill="currentColor"
        fillOpacity={1}
        fillRule="nonzero"
        stroke="none"
      />
      <path
        d="M76.941 14.043a4.96 4.96 0 01-4.96-4.961 4.958 4.958 0 014.96-4.96 4.957 4.957 0 014.957 4.96 4.96 4.96 0 01-4.957 4.961M27.668 98.805a4.96 4.96 0 110-9.922 4.958 4.958 0 014.96 4.96 4.96 4.96 0 01-4.96 4.962M76.941 0a9.08 9.08 0 00-9.082 9.082c0 1.07.27 2.066.61 3.02L25.863 85.125c-4.12.863-7.277 4.344-7.277 8.719a9.082 9.082 0 0018.164 0c0-1.008-.27-1.93-.57-2.836L79 17.746c3.992-.957 7.023-4.379 7.023-8.664 0-5.016-4.07-9.082-9.082-9.082M9.082 29.227a4.963 4.963 0 014.961 4.96 4.96 4.96 0 11-4.96-4.96M9.081 43.27a9.082 9.082 0 009.082-9.082c0-1.004-.273-1.93-.574-2.836-1.203-3.606-4.5-6.247-8.508-6.247-.64 0-1.203.239-1.809.368C3.156 26.332 0 29.813 0 34.188a9.082 9.082 0 009.082 9.082M4.121 65.922a4.96 4.96 0 119.922 0 4.96 4.96 0 01-4.961 4.957 4.96 4.96 0 01-4.96-4.957m14.042 0c0-1.008-.273-1.93-.574-2.836-1.203-3.606-4.496-6.246-8.508-6.246-.64 0-1.203.238-1.809.363C3.156 58.066 0 61.547 0 65.922c0 5.012 4.066 9.082 9.082 9.082 5.016 0 9.082-4.07 9.082-9.082"
        fill="currentColor"
        fillOpacity={1}
        fillRule="nonzero"
        stroke="none"
      />
    </svg>
  );
};

interface ImportModalProps extends ModalProps {
  organizationId: string;
  projectName: string;
  // undefined when not using preferences
  workspaceName?: string;
  // undefined when using insomnia://app/import
  defaultProjectId?: string;
  // undefined when in workspace selection page
  defaultWorkspaceId?: string;
  from:
  | {
    type: 'file';
  }
  | {
    type: 'uri';
    defaultValue?: string;
  }
  | {
    type: 'clipboard';
  };
}

export const ImportModal: FC<ImportModalProps> = ({
  projectName,
  workspaceName,
  defaultProjectId,
  defaultWorkspaceId,
  organizationId,
  from,
  ...modalProps
}) => {
  const modalRef = useRef<ModalHandle>(null);
  const scanResourcesFetcher = useFetcher<ScanForResourcesActionResult>();
  const importFetcher = useFetcher<ImportResourcesActionResult>();
  useEffect(() => {
    modalRef.current?.show();
  }, []);

  // Hack to close modal when import is complete until useFetcher provides a better API https://github.com/remix-run/react-router/discussions/10013
  const prevImportFetcherState = usePrevious(importFetcher.state);
  useEffect(() => {
    if (
      prevImportFetcherState === 'loading' &&
      importFetcher.state === 'idle'
    ) {
      hideAllModals();
      modalProps.onHide?.();
    }
  }, [importFetcher.state, modalProps, prevImportFetcherState]);
  // allow workspace import if there is only one workspace
  const totalWorkspaces = scanResourcesFetcher.data?.workspaces?.length || 0;
  const shouldImportToWorkspace = !!defaultWorkspaceId && totalWorkspaces <= 1;
  const header = shouldImportToWorkspace ? `Import to "${workspaceName}" Workspace` : `Import to "${projectName}" Project`;
  return (
    <OverlayContainer>
      <Modal {...modalProps} ref={modalRef}>
        <ModalHeader>{header}</ModalHeader>
        {scanResourcesFetcher.data && scanResourcesFetcher.data.errors.length === 0 ? (
          <ImportResourcesForm
            organizationId={organizationId}
            defaultProjectId={defaultProjectId}
            defaultWorkspaceId={shouldImportToWorkspace ? defaultWorkspaceId : ''}
            scanResult={scanResourcesFetcher.data}
            errors={importFetcher.data?.errors}
            disabled={importFetcher.state !== 'idle'}
            onSubmit={e => {
              e.preventDefault();
              importFetcher.submit(e.currentTarget, {
                method: 'post',
                action: '/import/resources',
              });
            }}
          />
        ) : (
          <ScanResourcesForm
            from={from}
            errors={scanResourcesFetcher.data?.errors}
            onSubmit={e => {
              e.preventDefault();
              scanResourcesFetcher.submit(e.currentTarget, {
                method: 'post',
                action: '/import/scan',
              });
            }}
          />
        )}
      </Modal>
    </OverlayContainer>
  );
};

const ScanResourcesForm = ({
  onSubmit,
  from,
  errors,
}: {
  onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
  from?: ImportModalProps['from'];
  errors?: string[];
}) => {
  const id = useId();
  const [importFrom, setImportFrom] = useState(from?.type || 'uri');

  return (
    <Fragment>
      <form
        id={id}
        onSubmit={onSubmit}
        method="post"
        style={{
          display: 'flex',
          flexDirection: 'column',
          gap: 'var(--padding-sm)',
        }}
      >
        <Fieldset>
          <RadioGroup>
            <Radio
              onChange={() => setImportFrom('file')}
              name="importFrom"
              value="file"
              checked={importFrom === 'file'}
            >

              File
            </Radio>
            <Radio
              onChange={() => setImportFrom('uri')}
              name="importFrom"
              value="uri"
              checked={importFrom === 'uri'}
            >

              Url
            </Radio>
            <Radio
              onChange={() => setImportFrom('clipboard')}
              name="importFrom"
              value="clipboard"
              checked={importFrom === 'clipboard'}
            >

              Clipboard
            </Radio>
          </RadioGroup>
        </Fieldset>
        {importFrom === 'file' && <FileField />}
        {importFrom === 'uri' && (

              Url:
              <input
                type="text"
                name="uri"
                defaultValue={from?.type === 'uri' ? from.defaultValue : undefined}
                placeholder="https://website.com/insomnia-import.json"
              />

        )}
      </form>

        {errors && errors.length > 0 && (

              Error while scanning for resources to import:
              {errors[0]}

        )}

            Supported Formats

            <Pill>
              <InsomniaIcon />
              Insomnia
            </Pill>
            <Pill>
              <PostmanIcon />
              Postman
            </Pill>
            <Pill>
              <SwaggerIcon />
              Swagger
            </Pill>
            <Pill>
              <OpenAPIIcon />
              OpenAPI
            </Pill>
            <Pill>
              <CurlIcon />
              cURL
            </Pill>
            <Pill>

              HAR
            </Pill>
            <Pill>

              WSDL
            </Pill>

        <Button
          variant="contained"
          bg="surprise"
          type="submit"
          style={{
            height: '40px',
            gap: 'var(--padding-sm)',
          }}
          form={id}
          className="btn"
        >
           Scan
        </Button>

    </Fragment>
  );
};

const ImportTypeTitle = styled.div({
  display: 'flex',
  alignItems: 'center',
  gap: 'var(--padding-sm)',
});

const ImportResourcesForm = ({
  scanResult,
  defaultProjectId,
  defaultWorkspaceId,
  organizationId,
  onSubmit,
  errors,
  disabled,
}: {
  scanResult: ScanForResourcesActionResult;
  organizationId: string;
  defaultProjectId?: string;
  defaultWorkspaceId?: string;
  errors?: string[];
  onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void;
  disabled: boolean;
}
) => {
  const id = useId();

  return (
    <Fragment>

        <form
          onSubmit={onSubmit}
          method="post"
          action="/import/resources"
          id={id}
        >
          <input hidden name="organizationId" readOnly value={organizationId} />
          <input hidden name="projectId" readOnly value={defaultProjectId} />
          <input hidden name="workspaceId" readOnly value={defaultWorkspaceId} />
        </form>

                <ImportTypeTitle>
                  {scanResult.type?.id.includes('insomnia') && (
                    <Fragment>
                      <InsomniaIcon width={24} height={24} />
                      Insomnia
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('postman') && (
                    <Fragment>
                      <PostmanIcon width={24} height={24} />
                      Postman
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('swagger') && (
                    <Fragment>
                      <SwaggerIcon width={24} height={24} />
                      Swagger
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('openapi') && (
                    <Fragment>
                      <OpenAPIIcon width={24} height={24} />
                      OpenAPI
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('wsdl') && (
                    <Fragment>

                      WSDL
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('har') && (
                    <Fragment>

                      HAR
                    </Fragment>
                  )}
                  {scanResult.type?.id.includes('curl') && (
                    <Fragment>
                      <CurlIcon width={24} height={24} />
                      cURL
                    </Fragment>
                  )}{' '}
                  resources to be imported:
                </ImportTypeTitle>

            {scanResult.workspaces && scanResult.workspaces?.length > 0 && (

                  {scanResult.workspaces.length}{' '}
                  {scanResult.workspaces.length === 1 ? 'Workspace' : 'Workspaces'}

            )}
            {scanResult.requests && scanResult.requests?.length > 0 && (

                  {scanResult.requests.length}{' '}
                  {scanResult.requests.length === 1 ? 'Request' : 'Requests'}

            )}
            {scanResult.apiSpecs && scanResult.apiSpecs?.length > 0 && (

                    {scanResult.apiSpecs.length}{' '}
                    {scanResult.apiSpecs.length === 1 ? 'OpenAPI Spec' : 'OpenAPI Specs'}

            )}
            {scanResult.environments &&
              scanResult.environments.length > 0 && (

                  {scanResult.environments.length}{' '}
                  {scanResult.environments.length === 1
                    ? 'Environment'
                    : 'Environments'}
                  {' with '}
                  {scanResult.cookieJars?.length}{' '}
                  {scanResult.cookieJars?.length === 1 ? 'Cookie Jar' : 'Cookie Jars'}

            )}
            {scanResult.unitTestSuites &&
              scanResult.unitTestSuites?.length > 0 && (

                  {scanResult.unitTestSuites.length}{' '}
                  {scanResult.unitTestSuites.length === 1
                    ? 'Test Suite'
                    : 'Test Suites'}
                  {' with '}
                  {scanResult.unitTests?.length}
                  {scanResult.unitTests?.length === 1 ? ' Test' : ' Tests'}

            )}

        {errors && errors.length > 0 && (

              Error while importing to Insomnia:
              {errors[0]}

        )}

        <Button
          variant="contained"
          bg="surprise"
          type="submit"
          disabled={disabled}
          style={{
            height: '40px',
            gap: 'var(--padding-sm)',
          }}
          form={id}
          className="btn"
        >
          {disabled ? (

               Importing

          ) : (

               Import

          )}
        </Button>

    </Fragment>
  );
};


import clone from 'clone';
import orderedJSON from 'json-order';

import * as models from '../models';
import type { CookieJar } from '../models/cookie-jar';
import type { Environment } from '../models/environment';
import type { GrpcRequest, GrpcRequestBody } from '../models/grpc-request';
import { isProject, Project } from '../models/project';
import type { Request } from '../models/request';
import { isRequestGroup, RequestGroup } from '../models/request-group';
import { WebSocketRequest } from '../models/websocket-request';
import { isWorkspace, Workspace } from '../models/workspace';
import * as templating from '../templating';
import * as templatingUtils from '../templating/utils';
import { setDefaultProtocol } from '../utils/url/protocol';
import { CONTENT_TYPE_GRAPHQL, JSON_ORDER_SEPARATOR } from './constants';
import { database as db } from './database';

export const KEEP_ON_ERROR = 'keep';
export const THROW_ON_ERROR = 'throw';
export type RenderPurpose = 'send' | 'general' | 'no-render';
export const RENDER_PURPOSE_SEND: RenderPurpose = 'send';
export const RENDER_PURPOSE_GENERAL: RenderPurpose = 'general';
export const RENDER_PURPOSE_NO_RENDER: RenderPurpose = 'no-render';

/** Key/value pairs to be provided to the render context */
export type ExtraRenderInfo = {
  name: string;
  value: any;
}[];

export type RenderedRequest = Request & {
  cookies: {
    name: string;
    value: string;
    disabled?: boolean;
  }[];
  cookieJar: CookieJar;
};

export type RenderedGrpcRequest = GrpcRequest;

export type RenderedGrpcRequestBody = GrpcRequestBody;

export interface RenderContextAndKeys {
  context: Record<string, any>;
  keys: {
    name: string;
    value: any;
  }[];
}

export type HandleGetRenderContext = () => Promise<RenderContextAndKeys>;

export type HandleRender = <T>(object: T, contextCacheKey?: string | null) => Promise<T>;

export async function buildRenderContext(
  {
    ancestors,
    rootEnvironment,
    subEnvironment,
    baseContext = {},
  }: {
    ancestors?: RenderContextAncestor[];
    rootEnvironment?: Environment;
    subEnvironment?: Environment;
    baseContext?: Record<string, any>;
  },
) {
  const envObjects: Record<string, any>[] = [];

  // Get root environment keys in correct order
  // Then get sub environment keys in correct order
  // Then get ancestor (folder) environment keys in correct order
  if (rootEnvironment) {
    const ordered = orderedJSON.order(
      rootEnvironment.data,
      rootEnvironment.dataPropertyOrder,
      JSON_ORDER_SEPARATOR,
    );
    envObjects.push(ordered);
  }

  if (subEnvironment) {
    const ordered = orderedJSON.order(
      subEnvironment.data,
      subEnvironment.dataPropertyOrder,
      JSON_ORDER_SEPARATOR,
    );
    envObjects.push(ordered);
  }

  for (const doc of (ancestors || []).reverse()) {
    const ancestor: any = doc;
    const { environment, environmentPropertyOrder } = ancestor;

    if (typeof environment === 'object' && environment !== null) {
      const ordered = orderedJSON.order(
        environment,
        environmentPropertyOrder,
        JSON_ORDER_SEPARATOR,
      );
      envObjects.push(ordered);
    }
  }

  // At this point, environments is a list of environments ordered
  // from top-most parent to bottom-most child, and they keys in each environment
  // ordered by its property map.
  // Do an Object.assign, but render each property as it overwrites. This
  // way we can keep same-name variables from the parent context.
  let renderContext = baseContext;

  // Made the rendering into a recursive function to handle nested Objects
  async function renderSubContext(
    subObject: Record<string, any>,
    subContext: Record<string, any>,
  ) {
    const keys = _getOrderedEnvironmentKeys(subObject);

    for (const key of keys) {
      /*
       * If we're overwriting a string, try to render it first using the same key from the base
       * environment to support same-variable recursion. This allows for the following scenario:
       *
       * base:  { base_url: 'google.com' }
       * obj:   { base_url: '{{ base_url }}/foo' }
       * final: { base_url: 'google.com/foo' }
       *
       * A regular Object.assign would yield { base_url: '{{ base_url }}/foo' } and the
       * original base_url of google.com would be lost.
       */
      if (Object.prototype.toString.call(subObject[key]) === '[object String]') {
        const isSelfRecursive = subObject[key].match(`{{ ?${key}[ |][^}]*}}`);

        if (isSelfRecursive) {
          // If we're overwriting a variable that contains itself, make sure we
          // render it first
          subContext[key] = await render(
            subObject[key],
            subContext, // Only render with key being overwritten
            null,
            KEEP_ON_ERROR,
            'Environment',
          );
        } else {
          // Otherwise it's just a regular replacement
          subContext[key] = subObject[key];
        }
      } else if (Object.prototype.toString.call(subContext[key]) === '[object Object]') {
        // Context is of Type object, Call this function recursively to handle nested objects.
        subContext[key] = await renderSubContext(subObject[key], subContext[key]);
      } else {
        // For all other Types, add the Object to the Context.
        subContext[key] = subObject[key];
      }
    }

    return subContext;
  }

  for (const envObject of envObjects) {
    // For every environment render the Objects
    renderContext = await renderSubContext(envObject, renderContext);
  }

  // Render the context with itself to fill in the rest.
  const finalRenderContext = renderContext;

  const keys = _getOrderedEnvironmentKeys(finalRenderContext);

  // Render recursive references and tags.
  const skipNextTime: Record<string, boolean> = {};

  for (let i = 0; i < 3; i++) {
    for (const key of keys) {
      // Skip rendering keys that stayed the same multiple times. This is here because
      // a render failure will leave the tag as-is and thus the next iteration of the
      // loop will try to re-render it again. We don't want to keep erroring on these
      // because renders are expensive and potentially not idempotent.
      if (skipNextTime[key]) {
        continue;
      }

      const renderResult = await render(
        finalRenderContext[key],
        finalRenderContext,
        null,
        KEEP_ON_ERROR,
        'Environment',
      );

      // Result didn't change, so skip
      if (renderResult === finalRenderContext[key]) {
        skipNextTime[key] = true;
        continue;
      }

      finalRenderContext[key] = renderResult;
    }
  }

  return finalRenderContext;
}

/**
 * Recursively render any JS object and return a new one
 * @param {*} obj - object to render
 * @param {object} context - context to render against
 * @param blacklistPathRegex - don't render these paths
 * @param errorMode - how to handle errors
 * @param name - name to include in error message
 * @return {Promise.<*>}
 */
export async function render<T>(
  obj: T,
  context: Record<string, any> = {},
  blacklistPathRegex: RegExp | null = null,
  errorMode: string = THROW_ON_ERROR,
  name = '',
) {
  // Make a deep copy so no one gets mad :)
  const newObj = clone(obj);

  async function next<T>(x: T, path: string, first = false) {
    if (blacklistPathRegex && path.match(blacklistPathRegex)) {
      return x;
    }

    const asStr = Object.prototype.toString.call(x);

    // Leave these types alone
    if (
      asStr === '[object Date]' ||
      asStr === '[object RegExp]' ||
      asStr === '[object Error]' ||
      asStr === '[object Boolean]' ||
      asStr === '[object Number]' ||
      asStr === '[object Null]' ||
      asStr === '[object Undefined]'
    ) {
      // Do nothing to these types
    } else if (typeof x === 'string') {
      try {
        // @ts-expect-error -- TSCONVERSION
        x = await templating.render(x, { context, path });

        // If the variable outputs a tag, render it again. This is a common use
        // case for environment variables:
        //   {{ foo }} => {% uuid 'v4' %} => dd265685-16a3-4d76-a59c-e8264c16835a
        // @ts-expect-error -- TSCONVERSION
        if (x.includes('{%')) {
          // @ts-expect-error -- TSCONVERSION
          x = await templating.render(x, { context, path });
        }
      } catch (err) {
        if (errorMode !== KEEP_ON_ERROR) {
          throw err;
        }
      }
    } else if (Array.isArray(x)) {
      for (let i = 0; i < x.length; i++) {
        x[i] = await next(x[i], `${path}[${i}]`);
      }
    } else if (typeof x === 'object' && x !== null) {
      // Don't even try rendering disabled objects
      // Note, this logic probably shouldn't be here, but w/e for now
      // @ts-expect-error -- TSCONVERSION
      if (x.disabled) {
        return x;
      }

      const keys = Object.keys(x);

      for (const key of keys) {
        if (first && key.indexOf('_') === 0) {
          // @ts-expect-error -- mapping unsoundness
          x[key] = await next(x[key], path);
        } else {
          const pathPrefix = path ? path + '.' : '';
          // @ts-expect-error -- mapping unsoundness
          x[key] = await next(x[key], `${pathPrefix}${key}`);
        }
      }
    }

    return x;
  }

  return next<T>(newObj, name, true);
}

interface RenderRequest<T extends Request | GrpcRequest | WebSocketRequest> {
  request: T;
}

interface BaseRenderContextOptions {
  environmentId?: string;
  purpose?: RenderPurpose;
  extraInfo?: ExtraRenderInfo;
}

interface RenderContextOptions extends BaseRenderContextOptions, Partial<RenderRequest<Request | GrpcRequest | WebSocketRequest>> {
  ancestors?: RenderContextAncestor[];
}
export async function getRenderContext(
  {
    request,
    environmentId,
    ancestors: _ancestors,
    purpose,
    extraInfo,
  }: RenderContextOptions,
): Promise<Record<string, any>> {
  const ancestors = _ancestors || await getRenderContextAncestors(request);

  const project = ancestors.find(isProject);
  const workspace = ancestors.find(isWorkspace);

  if (!workspace) {
    throw new Error('Failed to render. Could not find workspace');
  }

  const rootEnvironment = await models.environment.getOrCreateForParentId(
    workspace ? workspace._id : 'n/a',
  );
  const subEnvironment = await models.environment.getById(environmentId || 'n/a');
  const keySource: Record<string, string> = {};

  // Function that gets Keys and stores their Source location
  function getKeySource(subObject: string | Record<string, any>, inKey: string, inSource: string) {
    // Add key to map if it's not root
    if (inKey) {
      keySource[templatingUtils.normalizeToDotAndBracketNotation(inKey)] = inSource;
    }

    // Recurse down for Objects and Arrays
    const typeStr = Object.prototype.toString.call(subObject);

    if (typeStr === '[object Object]') {
      for (const key of Object.keys(subObject)) {
        // @ts-expect-error -- mapping unsoundness
        getKeySource(subObject[key], templatingUtils.forceBracketNotation(inKey, key), inSource);
      }
    } else if (typeStr === '[object Array]') {
      for (let i = 0; i < subObject.length; i++) {
        // @ts-expect-error -- mapping unsoundness
        getKeySource(subObject[i], templatingUtils.forceBracketNotation(inKey, i), inSource);
      }
    }
  }

  const inKey = templating.NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME;
  // Get Keys from root environment
  getKeySource((rootEnvironment || {}).data, inKey, 'root');

  // Get Keys from sub environment
  if (subEnvironment) {
    getKeySource(subEnvironment.data || {}, inKey, subEnvironment.name || '');
  }

  // Get Keys from ancestors (e.g. Folders)
  if (ancestors) {
    for (let index = 0; index < ancestors.length; index++) {
      const ancestor: any = ancestors[index] || {};

      if (
        isRequestGroup(ancestor) &&
        ancestor.hasOwnProperty('environment') &&
        ancestor.hasOwnProperty('name')
      ) {
        getKeySource(ancestor.environment || {}, inKey, ancestor.name || '');
      }
    }
  }

  // Add meta data helper function
  const baseContext: Record<string, any> = {};

  baseContext.getMeta = () => ({
    requestId: request ? request._id : null,
    workspaceId: workspace ? workspace._id : 'n/a',
  });

  baseContext.getKeysContext = () => ({
    keyContext: keySource,
  });

  baseContext.getPurpose = () => purpose;

  baseContext.getExtraInfo = (key: string) => {
    if (!Array.isArray(extraInfo)) {
      return null;
    }

    const p = extraInfo.find(v => v.name === key);
    return p ? p.value : null;
  };

  baseContext.getEnvironmentId = () => environmentId;

  // It is possible for a project to not exist because this code path can be reached via Inso/insomnia-send-request which has no concept of a project.
  baseContext.getProjectId = () => project?._id;

  // Generate the context we need to render
  return buildRenderContext({ ancestors, rootEnvironment, subEnvironment: subEnvironment || undefined, baseContext });
}

interface RenderGrpcRequestOptions extends BaseRenderContextOptions, RenderRequest<GrpcRequest> {
  skipBody?: boolean;
}
export async function getRenderedGrpcRequest(
  {
    purpose,
    extraInfo,
    request,
    environmentId,
    skipBody,
  }: RenderGrpcRequestOptions,
) {
  const renderContext = await getRenderContext({ request, environmentId, purpose, extraInfo });
  const description = request.description;
  // Render description separately because it's lower priority
  request.description = '';
  // Ignore body by default and only include if specified to
  const ignorePathRegex = skipBody ? /^body.*/ : null;
  // Render all request properties
  const renderedRequest: RenderedGrpcRequest = await render(
    request,
    renderContext,
    ignorePathRegex,
  );
  renderedRequest.description = await render(description, renderContext, null, KEEP_ON_ERROR);
  return renderedRequest;
}

type RenderGrpcRequestMessageOptions = BaseRenderContextOptions & RenderRequest<GrpcRequest>;
export async function getRenderedGrpcRequestMessage(
  {
    environmentId,
    request,
    extraInfo,
    purpose,
  }: RenderGrpcRequestMessageOptions,
) {
  const renderContext = await getRenderContext({ request, environmentId, purpose, extraInfo });
  // Render request body
  const renderedBody: RenderedGrpcRequestBody = await render(request.body, renderContext);
  return renderedBody;
}

type RenderRequestOptions = BaseRenderContextOptions & RenderRequest<Request>;
export interface RequestAndContext {
  request: RenderedRequest;
  context: Record<string, any>;
}
export async function getRenderedRequestAndContext(
  {
    request,
    environmentId,
    extraInfo,
    purpose,
  }: RenderRequestOptions,
): Promise<RequestAndContext> {
  const ancestors = await getRenderContextAncestors(request);
  const workspace = ancestors.find(isWorkspace);
  const parentId = workspace ? workspace._id : 'n/a';
  const cookieJar = await models.cookieJar.getOrCreateForParentId(parentId);
  const renderContext = await getRenderContext({ request, environmentId, ancestors, purpose, extraInfo });

  // HACK: Switch '#}' to '# }' to prevent Nunjucks from barfing
  // https://github.com/kong/insomnia/issues/895
  try {
    if (request.body.text && request.body.mimeType === CONTENT_TYPE_GRAPHQL) {
      const o = JSON.parse(request.body.text);
      o.query = o.query.replace(/#}/g, '# }');
      request.body.text = JSON.stringify(o);
    }
  } catch (err) { }

  // Render description separately because it's lower priority
  const description = request.description;
  request.description = '';
  // Render all request properties
  const renderResult = await render(
    {
      _request: request,
      _cookieJar: cookieJar,
    },
    renderContext,
    request.settingDisableRenderRequestBody ? /^body.*/ : null,
  );
  const renderedRequest = renderResult._request;
  const renderedCookieJar = renderResult._cookieJar;
  renderedRequest.description = await render(description, renderContext, null, KEEP_ON_ERROR);
  // Remove disabled params
  renderedRequest.parameters = renderedRequest.parameters.filter(p => !p.disabled);
  // Remove disabled headers
  renderedRequest.headers = renderedRequest.headers.filter(p => !p.disabled);

  // Remove disabled body params
  if (renderedRequest.body && Array.isArray(renderedRequest.body.params)) {
    renderedRequest.body.params = renderedRequest.body.params.filter(p => !p.disabled);
  }

  // Remove disabled authentication
  if (renderedRequest.authentication && renderedRequest.authentication.disabled) {
    renderedRequest.authentication = {};
  }

  // Default the proto if it doesn't exist
  renderedRequest.url = setDefaultProtocol(renderedRequest.url);
  return {
    context: renderContext,
    request: {
      cookieJar: renderedCookieJar,
      cookies: [],
      isPrivate: false,
      _id: renderedRequest._id,
      authentication: renderedRequest.authentication,
      body: renderedRequest.body,
      created: renderedRequest.created,
      modified: renderedRequest.modified,
      description: renderedRequest.description,
      headers: renderedRequest.headers,
      metaSortKey: renderedRequest.metaSortKey,
      method: renderedRequest.method,
      name: renderedRequest.name,
      parameters: renderedRequest.parameters,
      parentId: renderedRequest.parentId,
      settingDisableRenderRequestBody: renderedRequest.settingDisableRenderRequestBody,
      settingEncodeUrl: renderedRequest.settingEncodeUrl,
      settingSendCookies: renderedRequest.settingSendCookies,
      settingStoreCookies: renderedRequest.settingStoreCookies,
      settingRebuildPath: renderedRequest.settingRebuildPath,
      settingFollowRedirects: renderedRequest.settingFollowRedirects,
      type: renderedRequest.type,
      url: renderedRequest.url,
    },
  };
}

/**
 * Sort the keys that may have Nunjucks last, so that other keys get
 * defined first. Very important if env variables defined in same obj
 * (eg. {"foo": "{{ bar }}", "bar": "Hello World!"})
 *
 * @param v
 * @returns {number}
 */
function _nunjucksSortValue(v: string) {
  return v?.match?.(/({{|{%)/) ? 2 : 1;
}

function _getOrderedEnvironmentKeys(finalRenderContext: Record<string, any>): string[] {
  return Object.keys(finalRenderContext).sort((k1, k2) => {
    const k1Sort = _nunjucksSortValue(finalRenderContext[k1]);

    const k2Sort = _nunjucksSortValue(finalRenderContext[k2]);

    return k1Sort - k2Sort;
  });
}

type RenderContextAncestor = Request | GrpcRequest | WebSocketRequest | RequestGroup | Workspace | Project;
export async function getRenderContextAncestors(base?: Request | GrpcRequest | WebSocketRequest | Workspace): Promise<RenderContextAncestor[]> {
  return await db.withAncestors<RenderContextAncestor>(base || null, [
    models.request.type,
    models.grpcRequest.type,
    models.webSocketRequest.type,
    models.requestGroup.type,
    models.workspace.type,
    models.project.type,
  ]);
}


import { Modal, ModalHandle, ModalProps } from '../base/modal';

import { ModalHeader } from '../base/modal-header';

import { Button } from '../themed-button';

keep keep keep keep replace keep keep keep keep

// Import
import { ActionFunction, redirect } from 'react-router-dom';

import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../common/constants';
import { fetchImportContentFromURI, importResourcesToProject, importResourcesToWorkspace, scanResources, ScanResult } from '../../common/import';
import * as models from '../../models';
import { DEFAULT_PROJECT_ID } from '../../models/project';
import { invariant } from '../../utils/invariant';

export interface ScanForResourcesActionResult extends ScanResult { }

export const scanForResourcesAction: ActionFunction = async ({ request }): Promise<ScanForResourcesActionResult> => {
  const formData = await request.formData();

  const source = formData.get('importFrom');
  invariant(typeof source === 'string', 'Source is required.');
  invariant(['file', 'uri', 'clipboard'].includes(source), 'Unsupported import type');

  let content = '';
  if (source === 'uri') {
    const uri = formData.get('uri');
    if (typeof uri !== 'string' || uri === '') {
      return {
        errors: ['URI is required'],
      };
    }

    content = await fetchImportContentFromURI({
      uri,
    });
  } else if (source === 'file') {
    const filePath = formData.get('filePath');
    if (typeof filePath !== 'string' || filePath === '') {
      return {
        errors: ['File is required'],
      };
    }
    const uri = `file://${filePath}`;

    content = await fetchImportContentFromURI({
      uri,
    });
  } else {
    content = window.clipboard.readText();
  }

  if (!content) {
    return {
      errors: ['No content to import'],
    };
  }

  const result = await scanResources({ content });

  return result;
};

export interface ImportResourcesActionResult {
  errors?: string[];
}

export const importResourcesAction: ActionFunction = async ({ request }): Promise<ImportResourcesActionResult | Response> => {
  const formData = await request.formData();

  const organizationId = formData.get('organizationId');
  let projectId = formData.get('projectId');
  const workspaceId = formData.get('workspaceId');

  invariant(typeof organizationId === 'string', 'OrganizationId is required.');
  // when importing through insomnia://app/import, projectId is not provided
  if (typeof projectId !== 'string' || !projectId) {
    projectId = DEFAULT_PROJECT_ID;
  }

  const project = await models.project.getById(projectId);
  invariant(project, 'Project not found.');
  if (typeof workspaceId === 'string' && workspaceId) {
    const result = await importResourcesToWorkspace({
      workspaceId: workspaceId,
    });
    return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${result.workspace._id}/${result.workspace.scope === 'design'
      ? ACTIVITY_SPEC : ACTIVITY_DEBUG}`);
  }

  await importResourcesToProject({ projectId: project._id });
  return redirect(`/organization/${organizationId}/project/${projectId}`);
};


import 'codemirror/mode/clike/clike';
import 'codemirror/mode/clojure/clojure';
import 'codemirror/mode/css/css';
import 'codemirror/mode/go/go';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/http/http';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/mode/mllike/mllike';
import 'codemirror/mode/php/php';
import 'codemirror/mode/powershell/powershell';
import 'codemirror/mode/python/python';
import 'codemirror/mode/ruby/ruby';
import 'codemirror/mode/shell/shell';
import 'codemirror/mode/swift/swift';
import 'codemirror/mode/yaml/yaml';
import 'codemirror/addon/display/autorefresh';
import 'codemirror/addon/dialog/dialog';
import 'codemirror/addon/dialog/dialog.css';
import 'codemirror/addon/fold/foldcode';
import 'codemirror/addon/fold/foldgutter';
import 'codemirror/addon/fold/foldgutter.css';
import 'codemirror/addon/fold/brace-fold';
import 'codemirror/addon/fold/comment-fold';
import 'codemirror/addon/fold/indent-fold';
import 'codemirror/addon/fold/xml-fold';
import 'codemirror/addon/hint/show-hint';
import 'codemirror/addon/hint/show-hint.css';
import 'codemirror/addon/comment/comment';
import 'codemirror/addon/search/search';
import 'codemirror/addon/search/searchcursor';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/edit/closebrackets';
import 'codemirror/addon/search/matchesonscrollbar';
import 'codemirror/addon/search/matchesonscrollbar.css';
import 'codemirror/addon/selection/active-line';
import 'codemirror/addon/selection/selection-pointer';
import 'codemirror/addon/display/placeholder';
import 'codemirror/addon/lint/lint';

// for the code that uses this yaml parser, see https://github.com/codemirror/CodeMirror/blob/master/addon/lint/yaml-lint.js
import * as jsyaml from 'js-yaml';
global.jsyaml = jsyaml;
import 'codemirror/addon/lint/yaml-lint';
/**/
import 'codemirror/addon/lint/lint.css';
import 'codemirror/keymap/vim';
import 'codemirror/keymap/emacs';
import 'codemirror/keymap/sublime';
import 'codemirror-graphql/hint';
import 'codemirror-graphql/lint';
import 'codemirror-graphql/info';
import 'codemirror-graphql/jump';
import 'codemirror-graphql/mode';
import 'codemirror-graphql/variables/lint';
import 'codemirror-graphql/variables/mode';
import './modes/nunjucks';
import './modes/curl';
import './modes/openapi';
import './lint/javascript-async-lint';
import './lint/json-lint';
import './extensions/autocomplete';
import './extensions/clickable';
import './extensions/nunjucks-tags';
// CSS
import 'codemirror/lib/codemirror.css';
import '../../css/editor/index.less';