import {LitElement, html, unsafeHTML, css, guard, until} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as polyline from './polyline.js';
import {gpx_parse} from './gpx.js';

const k_client_id = '28276';
const k_redirect_url = 'https://tildefriends.net/~cory/gg/login';

const k_color_snow = [128, 128, 255, 255];
const k_color_ice = [160, 160, 255, 255];
const k_color_water = [0, 0, 255, 255];
const k_color_dirt = [128, 129, 130, 255];
const k_color_pavement = [32, 32, 32, 255];
const k_color_grass = [0, 255, 0, 255];
const k_color_default = [128, 128, 128, 255];

const k_store = {
	'๐Ÿฆž': 15,
	'๐Ÿ›ถ': 10,
	'๐Ÿ ': 10,
	'โ›ฐ': 10,
	'๐Ÿ ': 10,
};

const k_marker_snap = {x: 5, y: 4};

class GgAppElement extends LitElement {
	static get properties() {
		return {
			user: {type: Object},
			strava: {type: Object},
			activities: {type: Array},
			activity: {type: Object},
			world: {type: Object},
			whoami: {type: String},
			status: {type: Object},
			tab: {type: String},
			url: {type: String},
			currency: {type: Number},
			to_build: {type: String},
			emoji_of_the_day: {type: String},
		};
	}

	constructor() {
		super();
		this.activities = [];
		this.activity = {};
		this.loaded_activities = [];
		this.placed_emojis = [];
		this.strava = {};
		this.min_lat = Number.MAX_VALUE;
		this.min_lon = Number.MAX_VALUE;
		this.max_lat = -Number.MAX_VALUE;
		this.max_lon = -Number.MAX_VALUE;
		this.focus = undefined;
		this.status = undefined;
		this.tab = 'map';
		this.load().catch(function(e) {
			console.log('load error', e);
		});
		this.to_build = '๐Ÿ ';
	}

	async load() {
		console.log('load');
		let emojis = await (await fetch('emojis.json')).json();
		emojis = Object.values(emojis).map(x => Object.values(x)).flat();
		let today = new Date();
		let date_index = today.getYear() * 356 + today.getMonth() * 31 + today.getDate();
		this.emoji_of_the_day = emojis[(date_index * 123457) % emojis.length];
		this.user = await tfrpc.rpc.getUser();
		this.url = (await tfrpc.rpc.url()).split('?')[0];
		try {
			await this.update_credentials();
		} catch (e) {
			console.log('update_credentials failed', e);
		}
		try {
			await this.update_activities();
		} catch (e) {
			console.log('update_activities failed', e);
		}
		await this.acquire_ssb_identity();
		if (this.whoami && this.activities?.length) {
			await this.sync_activities();
		}
		await this.get_activities_from_ssb();
	}

	/* https://gist.github.com/jcouyang/632709f30e12a7879a73e9e132c0d56b?permalink_comment_id=3591045#gistcomment-3591045 */
	async promise_all(promises, max_concurrent) {
		let index = 0;
		let results = [];
		async function exec_thread() {
			while (index < promises.length) {
				const current = index++;
				results[current] = await promises[current];
			}
		}
		const threads = [];
		for (let thread = 0; thread < max_concurrent; thread++) {
			threads.push(exec_thread());
		}
		await Promise.all(threads);
		return results;
	}

	async get_activities_from_ssb() {
		this.status = {text: 'loading activities'};
		this.loaded_activities = [];
		let rows = await tfrpc.rpc.query(`
			SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id
			FROM messages_fts('"gg-activity"')
			JOIN messages ON messages.rowid = messages_fts.rowid,
				json_each(messages.content, '$.mentions') as mention
			WHERE json_extract(messages.content, '$.type') = 'gg-activity' AND
			json_extract(mention.value, '$.name') = 'activity_data'
			ORDER BY messages.timestamp DESC
		`, []);
		this.status = {text: 'loading activity data'};
		let authors = rows.map(x => x.author);
		let blobs = await this.promise_all(rows.map(x => tfrpc.rpc.get_blob(x.blob_id)), 8);
		this.status = {text: 'processing activity data'};
		for (let [index, blob] of blobs.entries()) {
			let activity;
			try {
				activity = JSON.parse(blob);
			} catch {
				activity = gpx_parse(blob);
			}
			if (activity) {
				activity.author = authors[index];
				this.loaded_activities.push(activity);
			}
		}
		this.status = {text: 'calculating balance'};
		rows = await tfrpc.rpc.query(`
			SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity'
		`, [this.whoami]);
		let currency = rows[0].currency;
		rows = await tfrpc.rpc.query(`
			SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place'
		`, [this.whoami]);
		let spent = rows[0].cost;
		this.currency = currency - spent;
		this.status = {text: 'getting placed emojis'};
		rows = await tfrpc.rpc.query(`
			SELECT messages.content
			FROM messages_fts('"gg-place"')
			JOIN messages ON messages.rowid = messages_fts.rowid
			WHERE json_extract(messages.content, '$.type') = 'gg-place'
			ORDER BY messages.timestamp
		`);
		for (let row of rows) {
			console.log(row.content);
			let content = JSON.parse(row.content);
			this.placed_emojis.push({
				position: content.position,
				emoji: content.emoji,
			});
		}
		console.log(this.placed_emojis);
		this.status = undefined;
		this.update_map();
	}

	async sync_activities() {
		let ids = this.activities.map(x => `https://www.strava.com/activities/${x.id}`);
		let missing = await tfrpc.rpc.query(`
			WITH my_activities AS (
				SELECT json_extract(mention.value, '$.link') AS url
				FROM messages, json_each(messages.content, '$.mentions') AS mention
				WHERE
					author = ? AND
					json_extract(messages.content, '$.type') = 'gg-activity' AND
					json_extract(mention.value, '$.name') = 'activity_url')
			SELECT from_strava.value FROM json_each(?) AS from_strava
			LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url
			WHERE my_activities.url IS NULL
			`, [this.whoami, JSON.stringify(ids)]);
		console.log('missing = ', missing);
		for (let [index, row] of missing.entries()) {
			this.status = {text: 'syncing from strava', value: index, max: missing.length};
			let url = row.value;
			let id = url.match(/.*\/(\d+)/)[1];
			let response = await fetch(`https://www.strava.com/api/v3/activities/${id}`, {
				headers: {
					'Authorization': `Bearer ${this.strava.access_token}`,
				},
			});
			let activity = await response.json();
			let blob_id = await tfrpc.rpc.store_blob(JSON.stringify(activity));
			let message = {
				type: 'gg-activity',
				mentions: [
					{
						link: url,
						name: 'activity_url',
					},
					{
						link: blob_id,
						name: 'activity_data',
					}
				],
			};
			await tfrpc.rpc.appendMessage(this.whoami, message);
		}
		this.status = undefined;
	}

	async acquire_ssb_identity() {
		let user = await tfrpc.rpc.getUser();
		if (!user?.credentials?.session?.name) {
			return;
		}
		let ids = await tfrpc.rpc.getIdentities();
		let players = ids.length ? (await tfrpc.rpc.query(`
			SELECT author FROM messages JOIN json_each(?) ON messages.author = json_each.value
			WHERE
				json_extract(messages.content, '$.type') = 'gg-player' AND
				json_extract(messages.content, '$.active')
			ORDER BY timestamp DESC limit 1
			`, [JSON.stringify(ids)])).map(row => row.author) : [];
		if (!players.length) {
			this.whoami = await tfrpc.rpc.createIdentity();
			if (this.whoami) {
				await tfrpc.rpc.appendMessage(this.whoami, {
					type: 'gg-player',
					active: true,
				});
			}
		} else {
			players.sort();
			this.whoami = players[0];
		}
	}

	async update_credentials() {
		let name = this.user?.credentials?.session?.name;
		if (!name) {
			return;
		}
		let shared = await tfrpc.rpc.sharedDatabaseGet(name);
		if (shared) {
			await tfrpc.rpc.databaseSet('strava', shared);
			await tfrpc.rpc.sharedDatabaseRemove(name);
		}
		this.strava = JSON.parse(await tfrpc.rpc.databaseGet('strava') || '{}');
		if (new Date().valueOf() / 1000 > this.strava.expires_at) {
			console.log('this looks expired', new Date().valueOf() / 1000, '>', this.strava.expires_at);
			let x = await tfrpc.rpc.refresh_token(this.strava);
			if (x) {
				this.strava = x;
				await tfrpc.rpc.databaseSet('strava', JSON.stringify(x));
			} else {
				this.strava = null;
			}
		}
	}

	async update_activities() {
		if (this?.strava?.access_token) {
			let response = await fetch('https://www.strava.com/api/v3/athlete/activities', {
				headers: {
					'Authorization': `Bearer ${this.strava.access_token}`,
				},
			});
			this.activities = await response.json();
			this.activities.sort((a, b) => (a.id - b.id));
		}
	}

	color_to_emoji(color) {
		const k_map = [
			[k_color_snow, 'โฌœ'],
			[k_color_ice, '๐ŸŸฆ'],
			[k_color_water, '๐ŸŸฆ'],
			[k_color_dirt, '๐ŸŸซ'],
			[k_color_pavement, 'โฌ›'],
			[k_color_grass, '๐ŸŸฉ'],
			[k_color_default, '๐ŸŸง'],
		];
		for (let m of k_map) {
			if (m[0][0] == color[0] &&
				m[0][1] == color[1] &&
				m[0][2] == color[2] &&
				m[0][3] == color[3]) {
				return m[1];
			}
		}
	}

	activity_bounds(activity) {
		let min_lat = Number.MAX_VALUE;
		let min_lon = Number.MAX_VALUE;
		let max_lat = -Number.MAX_VALUE;
		let max_lon = -Number.MAX_VALUE;
		if (activity?.map?.polyline) {
			for (let pt of polyline.decode(activity.map.polyline)) {
				min_lat = Math.min(min_lat, pt[0]);
				min_lon = Math.min(min_lon, pt[1]);
				max_lat = Math.max(max_lat, pt[0]);
				max_lon = Math.max(max_lon, pt[1]);
			}
		}
		if (activity?.segments) {
			for (let segment of activity.segments) {
				for (let pt of segment) {
					min_lat = Math.min(min_lat, pt.lat);
					min_lon = Math.min(min_lon, pt.lon);
					max_lat = Math.max(max_lat, pt.lat);
					max_lon = Math.max(max_lon, pt.lon);
				}
			}
		}
		return {
			min: {
				lat: min_lat,
				lng: min_lon,
			},
			max: {
				lat: max_lat,
				lng: max_lon,
			},
		};
	}

	on_click(event) {
		let popup = L.popup()
			.setLatLng(event.latlng)
			.setContent(`
				<div><a target="_top" href="https://www.google.com/maps/search/?api=1&query=${event.latlng.lat},${event.latlng.lng}">${event.latlng.lat}, ${event.latlng.lng}</a></div>
			`)
			.openOn(this.leaflet);
	}

	async build() {
		if (this.popup) {
			this.popup.remove();
		}
		if (!this.marker) {
			return;
		}
		let latlng = this.marker.getLatLng();

		let cost = k_store[this.to_build];
		if (cost > this.currency) {
			alert('Insufficient funds.');
			return;
		}
		let message = {
			type: 'gg-place',
			position: {lat: latlng.lat, lng: latlng.lng},
			emoji: this.to_build,
			cost: cost,
		};
		let id = await tfrpc.rpc.appendMessage(this.whoami, message);
		this.marker.remove();
		this.placed_emojis.push({
			position: {lat: latlng.lat, lng: latlng.lng},
			emoji: this.to_build,
		});
		this.currency -= cost;
		return this.update_map();
	}

	on_marker_click(event) {
		this.popup = L.popup()
			.setLatLng(event.latlng)
			.setContent(`
				${this.to_build} (-${k_store[this.to_build]}) <input type="button" value="Build" onclick="document.getElementById('ggapp').build()"></input>
			`)
			.openOn(this.leaflet);
	}

	snap_to_grid(latlng, fudge, zoom) {
		let position = this.leaflet.options.crs.latLngToPoint(latlng, zoom ?? this.leaflet.getZoom());
		position.x = Math.round(position.x / 16) * 16 + (fudge?.x ?? 0);
		position.y = Math.round(position.y / 16) * 16 + (fudge?.y ?? 0);
		position = this.leaflet.options.crs.pointToLatLng(position, zoom ?? this.leaflet.getZoom());
		return position;
	}

	on_marker_move(event) {
		if (!this.no_snap && this.marker) {
			this.no_snap = true;
			this.marker.setLatLng(this.snap_to_grid(this.marker.getLatLng(), k_marker_snap));
			this.no_snap = false;
		}
	}

	on_zoom(event) {
		if (this.marker) {
			this.marker.setLatLng(this.snap_to_grid(this.marker.getLatLng(), k_marker_snap));
		}
	}

	on_mouse_down(event) {
		if (this.marker) {
			this.marker.remove();
			this.marker = undefined;
		}

		if (this.to_build) {
			this.marker = L.marker(this.snap_to_grid(event.latlng, k_marker_snap), {icon: L.divIcon({className: 'build-icon'}), draggable: true}).addTo(this.leaflet);
			this.marker.on({click: this.on_marker_click.bind(this)});
			this.marker.on({drag: this.on_marker_move.bind(this)});
		}
	}

	async update_map() {
		let map = this.shadowRoot.getElementById('map');
		if (!map || !this.loaded_activities.length) {
			this.leaflet = undefined;
			this.grid_layer = undefined;
			return;
		}
		if (!this.leaflet) {
			this.leaflet = L.map(map, {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
			this.leaflet.on({contextmenu: this.on_click.bind(this)});
			this.leaflet.on({click: this.on_mouse_down.bind(this)});
			this.leaflet.on({zoom: this.on_zoom.bind(this)});
		}
		let self = this;
		let grid_layer = L.GridLayer.extend({
			createTile: function(coords) {
				var tile = L.DomUtil.create('canvas', 'leaflet-tile');
				var size = this.getTileSize();
				tile.width = size.x;
				tile.height = size.y;
				var context = tile.getContext('2d');
				context.font = '10pt sans';
				let bounds = this._tileCoordsToBounds(coords);
				let degrees = 360.0 / (2 ** coords.z);
				let ul = bounds.getNorthWest();
				let lr = bounds.getSouthEast();

				let mini = document.createElement('canvas');
				mini.width = Math.floor(size.x / 16.0);
				mini.height = Math.floor(size.y / 16.0);
				let mini_context = mini.getContext('2d');
				let image_data = context.getImageData(0, 0, mini.width, mini.height);
				for (let activity of self.loaded_activities) {
					self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity);
				}
				context.textAlign = 'left';
				context.textBaseline = 'bottom';
				for (let x = 0; x < mini.width; x++) {
					for (let y = 0; y < mini.height; y++) {
						let start = (y * mini.width + x) * 4;
						let pixel = self.color_to_emoji(image_data.data.slice(start, start + 4));
						if (pixel) {
							//context.fillRect(x * size.x / mini.width, y * size.y / mini.height, size.x / mini.width, size.y / mini.height);
							context.fillText(pixel, x * size.x / mini.width, y * size.y / mini.height + mini.height);
						}
					}
				}
				for (let placed of self.placed_emojis) {
					let position = self.leaflet.options.crs.latLngToPoint(self.snap_to_grid(placed.position, undefined, coords.z), coords.z);
					let tile_x = Math.floor(position.x / size.x);
					let tile_y = Math.floor(position.y / size.y);
					position.x = position.x - tile_x * size.x;
					position.y = position.y - tile_y * size.y;
					if (tile_x == coords.x && tile_y == coords.y) {
						//context.fillRect(position.x, position.y, size.x / mini.width, size.y / mini.height);
						context.fillText(placed.emoji, position.x, position.y + mini.height);
					}
				}
				return tile;
			}
		});
		if (this.grid_layer) {
			this.grid_layer.redraw();
		} else {
			this.grid_layer = new grid_layer();
			this.grid_layer.addTo(this.leaflet);
		}
		for (let activity of this.loaded_activities) {
			let bounds = this.activity_bounds(activity);
			this.min_lat = Math.min(this.min_lat, bounds.min.lat);
			this.min_lon = Math.min(this.min_lon, bounds.min.lng);
			this.max_lat = Math.max(this.max_lat, bounds.max.lat);
			this.max_lon = Math.max(this.max_lon, bounds.max.lng);
		}
		if (this.focus) {
			this.leaflet.fitBounds([
				this.focus.min,
				this.focus.max,
			]);
			this.focus = undefined;
		} else {
			this.leaflet.fitBounds([
				[this.min_lat, this.min_lon],
				[this.max_lat, this.max_lon],
			]);
		}
	}

	activity_to_color(activity) {
		let color = [0, 0, 0, 255];
		switch (activity.sport_type) {
			/* Implies snow. */
			case 'AlpineSki':
			case 'BackcountrySki':
			case 'NordicSki':
			case 'Snowshoe':
			case 'Snowboard':
				color = k_color_snow;
				break;

			/* Implies ice. */
			case 'IceSkate':
			case 'InlineSkate':
				color = k_color_ice;
				break;

			/* Implies water. */
			case 'Canoeing':
			case 'Kayaking':
			case 'Kitesurf':
			case 'Rowing':
			case 'Sail':
			case 'StandUpPaddling':
			case 'Surfing':
			case 'Swim':
			case 'Windsurf':
				color = k_color_water;
				break;

			/* Implies dirt. */
			case 'EMountainBikeRide':
			case 'Hike':
			case 'MountainBikeRide':
			case 'RockClimbing':
			case 'TrailRun':
				color = k_color_dirt;
				break;

			/* Implies pavement. */
			case 'EBikeRide':
			case 'GravelRide':
			case 'Handcycle':
			case 'Ride':
			case 'RollerSki':
			case 'Run':
			case 'Skateboard':
			case 'Badminton':
			case 'Tennis':
			case 'Velomobile':
			case 'Walk':
			case 'Wheelchair':
				color = k_color_pavement;
				break;

			/* Grass, maybe? */
			case 'Golf':
			case 'Soccer':
			case 'Squash':
				color = k_color_grass;
				break;

			// Crossfit,
			// Elliptical
			// HighIntensityIntervalTraining
			// Pickleball
			// Pilates
			// Racquetball
			// StairStepper
			// TableTennis,
			// VirtualRide
			// VirtualRow
			// VirtualRun
			// WeightTraining
			// Workout
			// Yoga
			default:
				color = k_color_default;
		}
		return color;
	}

	line(image_data, x0, y0, x1, y1, value) {
		/* <3 https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm */
		let dx = Math.abs(x1 - x0);
		let sx = x0 < x1 ? 1 : -1;
		let dy = -Math.abs(y1 - y0);
		let sy = y0 < y1 ? 1 : -1;
		let error = dx + dy;
		while (true) {
			if (x0 >= 0 && y0 >= 0 && x0 < image_data.width && y0 < image_data.height) {
				let base = (y0 * image_data.width + x0) * 4;
				image_data.data[base + 0] = value[0];
				image_data.data[base + 1] = value[1];
				image_data.data[base + 2] = value[2];
				image_data.data[base + 3] = value[3];
			}

			if (x0 == x1 && y0 == y1) {
				break;
			}
			let e2 = 2 * error;
			if (e2 >= dy) {
				if (x0 == x1) {
					break;
				}
				error += dy;
				x0 = Math.round(x0 + sx);
			}
			if (e2 <= dx) {
				if (y0 == y1) {
					break;
				}
				error += dx;
				y0 = Math.round(y0 + sy);
			}
		}
	}

	draw_activity_to_tile(image_data, width, height, ul, lr, activity) {
		let color = this.activity_to_color(activity);
		if (activity?.map?.polyline) {
			let last;
			for (let pt of polyline.decode(activity.map.polyline)) {
				let px = [
					Math.floor(width * (pt[1] - ul.lng) / (lr.lng - ul.lng)),
					Math.floor(height * (pt[0] - ul.lat) / (lr.lat - ul.lat)),
				];
				if (last) {
					this.line(image_data, last[0], last[1], px[0], px[1], color);
				}
				last = px;
			}
		}
		if (activity?.segments) {
			for (let segment of activity.segments) {
				let last;
				for (let pt of segment) {
					let px = [
						Math.floor(width * (pt.lon - ul.lng) / (lr.lng - ul.lng)),
						Math.floor(height * (pt.lat - ul.lat) / (lr.lat - ul.lat)),
					];
					if (last) {
						this.line(image_data, last[0], last[1], px[0], px[1], color);
					}
					last = px;
				}
			}
		}
	}

	async on_upload(event) {
		try {
			let file = event.srcElement.files[0];
			let xml = await file.text();
			let gpx = gpx_parse(xml);
			let blob_id = await tfrpc.rpc.store_blob(xml);
			console.log('blob_id = ', blob_id);
			console.log(gpx);
			let message = {
				type: 'gg-activity',
				mentions: [
					{
						link: `https://${gpx.link}/activity/${gpx.time}`,
						name: 'activity_url',
					},
					{
						link: blob_id,
						name: 'activity_data',
					}
				],
			};
			console.log('id =', this.whoami, 'message = ', message);
			let id = await tfrpc.rpc.appendMessage(this.whoami, message);
			console.log('appended message', id);
			alert('Activity uploaded.');
			await this.get_activities_from_ssb();
		} catch (e) {
			alert(`Error: ${JSON.stringify(e, null, 2)}`);
		}
	}

	upload() {
		let input = document.createElement('input');
		input.type = 'file';
		input.onchange = (event) => this.on_upload(event);
		input.click();
	}

	updated() {
		this.update_map();
	}

	focus_map(activity) {
		let bounds = this.activity_bounds(activity);
		if (bounds.min.lat < bounds.max.lat &&
			bounds.min.lng < bounds.max.lng) {
			this.tab = 'map';
			this.focus = bounds;
		}
	}

	render_news() {
		return html`
			<ul>
				${this.loaded_activities.map(x => html`
					<li style="cursor: pointer" @click=${() => this.focus_map(x)}>${x.author} ${x.name ?? x.time}</li>
				`)}
			</ul>
		`;
	}

	render_store_item(item) {
		let [emoji, cost] = item;
		return html`
			<div>
				<input type="button" value="${emoji}" @click=${() => this.to_build = emoji}></input> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined}
			</div>
		`;
	}

	render_store() {
		let store = Object.assign({}, k_store);
		store[this.emoji_of_the_day] = 5;
		return html`
			<h2>Store</h2>
			<div><b>Your balance:</b> ${this.currency}</div>
			${Object.entries(store).map(this.render_store_item.bind(this))}
		`;
	}

	render() {
		let header;
		if (!this.user?.credentials?.session?.name) {
			header = html`<div style="flex: 1 0">Please <a target="_top" href="/login?return=${this.url}">login</a> to Tilde Friends, first.</div>`;
		} else if (!this.strava?.access_token) {
			let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
			header = html`
				<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
					<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
					<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
					<input type="button" value="๐Ÿ“" @click=${this.upload}></input>
				</div>
			`;
		} else {
			header = html`
				<div>
					<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
						<h1>Welcome, ${this.user.credentials.session.name}</h1>
						<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.whoami}</span>
						<input type="button" value="๐Ÿ“" @click=${this.upload}></input>
					</div>
					<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3>
				</div>
			`;
		}

		let navigation = html`
			<style>
				#navigation input[type="button"] {
					min-width: 3em;
					min-height: 3em;
					flex: 1 0;
					font-size: large;
				}
			</style>
			<div id="navigation" style="display: flex; flex-direction: row">
				<input type="button" id="button_map" @click=${() => this.tab = 'map'} value="๐Ÿ—บ๏ธMap"></input>
				<input type="button" id="button_news" @click=${() => this.tab = 'news'} value="๐ŸƒNews"></input>
				<input type="button" id="button_friends" @click=${() => this.tab = 'friends'} value="๐Ÿ‘ซFriends"></input>
				<input type="button" id="button_store" @click=${() => this.tab = 'store'} value="๐Ÿ—๏ธStore"></input>
			</div>
		`;

		let content;
		switch (this.tab) {
			case 'map':
				content = html`<div id="map" style="width: 100%; height: 100%"></div>`;
				break;
			case 'news':
				content = this.render_news();
				break;
			case 'friends':
				content = html`<div>Friends</div>`;
				break;
			case 'store':
				content = this.render_store();
				break;
		}

		return html`
			<style>
			.build-icon::before {
				content: '๐Ÿ“';
				border: 2px solid red;
			}
			</style>
			<link rel="stylesheet" href="leaflet.css"/>
			<div style="width: 100%; height: 100%; display: flex; flex-direction: column">
				${header}
				<div style="flex: 1 0; overflow: scroll">${content}</div>
				${navigation}
			</div>
		`;
	}
}
customElements.define('gg-app', GgAppElement);