let p = console.log
import { err } from './utils'

import db from './db'
import state from './state'

import config from './config'
import { find, omit, orderBy } from 'lodash'
import { nanoid } from 'nanoid'
import fzi from 'fzi'
import { evaluate as eval_math } from 'mathjs'
import { cloneDeep } from 'lodash'

export default new class api

	def add_link text
		let link = await create_link_from_text text
		link.id = nanoid!
		await db.links.add link
		await reload_db!
		imba.commit!
		p omit(link, "icon")
		return link

	def update_link old_link, new_link_text
		let new_link = await create_link_from_text new_link_text
		new_link.frequency = old_link.frequency
		let result = await db.links.update old_link.id, new_link
		throw "Link id not found." if result is 0
		await reload_db!
		imba.commit!
		p omit(old_link, "icon")
		p omit(new_link, "icon")
		return new_link

	def put_link link
		try
			await db.links.update link.id, link
			if link.is_bang and config.data.default_bang.id is link.id
				config.set_default_bang link
			await reload_db!
		catch e
			err "putting link", e

	def delete_link link
		def go
			try
				await db.links.delete(link.id)
			catch e
				return err "deleting link", e
			try
				await reload_db!
			catch e
				return err "reloading db after successful delete", e
		state.loading = yes
		await go!
		state.link_selection_index = Math.min state.link_selection_index, state.sorted_links.length - 1
		state.loading = no

	def pin_link link
		link.is_pinned = !link.is_pinned
		try
			let result = await db.links.update link.id, link
			throw "Link id not found." if result is 0
		catch e
			return err "pinning link", e
		await reload_db!
		imba.commit!

	def reload_db
		state.links = await db.links.toArray()
		if state.active_bang
			let id = state.active_bang.id
			state.active_bang = find state.links, { id }
		let id = config.data.default_bang.id
		let link = find state.links, { id }
		if link
			config.data.default_bang = link
			config.save!
		sort_links!

	def increment_link_frequency link
		link.frequency += 1
		try
			await put_link link
		catch e
			err "putting link", e

	def sort_links
		if state.query.trim!.length <= 0
			state.sorted_links = orderBy(state.links, ['is_pinned', 'frequency'], ['desc', 'desc'])
		elif config.data.enable_effective_names
			state.sorted_links = fzi.sort state.query, state.links, do |x| x.name
		else
			state.sorted_links = fzi.sort state.query, state.links, do |x| x.display_name

	def name_exists new_name
		state.links.some! do |{name}| new_name is name

	def add_initial_links
		let initial_links = [
			"tutorial github.com/familyfriendlymikey/fuzzyhome"
			"!brave search `b search.brave.com/search?q="
			"!youtube youtube.com/results?search_query="
			"photopea photopea.com"
			"twitch twitch.tv"
			"messenger `me messenger.com"
			"instagram `in instagram.com"
			"localhost `3000 http://localhost:3000"
		]
		for link_text in initial_links
			try
				add_link link_text
			catch e
				err "adding link", e

	def create_link_from_text text, get_icon=yes
		text = text.trim!
		throw "Text is empty." if text is ''
		let split_text = text.split(/\s+/)
		throw "No url provided." if split_text.length < 2
		let url = split_text.pop!
		let host
		{ href:url, host } = parse_url url
		let name
		if split_text[-1].startsWith "`"
			name = split_text.pop!.slice(1)
		let display_name = split_text.join(" ")
		let is_bang = no
		let is_pinned = no
		if display_name.startsWith "!"
			is_bang = yes
			display_name = display_name.slice(1)
		name ||= display_name
		let link = { name, display_name, is_bang, is_pinned, url, frequency:0, history:[] }
		if get_icon
			link.icon = await fetch_image_as_base_64 host
		return link

	def fetch_image_as_base_64 host
		let fallback = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAH0lEQVR42mO8seXffwYqAsZRA0cNHDVw1MBRA0eqgQCDRkbJSQHxEQAAAABJRU5ErkJggg=='
		return new Promise! do |resolve|
			let res
			try
				res = await global.fetch("https://icon.horse/icon/{host}")
			catch
				p "Failed to get icon from icon horse."
				return resolve fallback
			# todo: can i use .text() on this or something
			let blob = await res.blob!
			let reader = new FileReader!
			reader.onload = do
				resolve this.result
			reader.onerror = do
				p "Failed to get data from reader."
				resolve fallback
				return
			reader.readAsDataURL(blob)

	def toggle_effective_names
		config.data.enable_effective_names = !config.data.enable_effective_names
		config.save!
		sort_links!

	def construct_link_text link
		link.display_name = link.display_name.trim!
		link.name = link.name.trim!
		link.url = link.url.trim!
		let link_text = ""
		link_text += "!" if link.is_bang
		link_text += link.display_name
		link_text += " `{link.name}" if link.name isnt link.display_name
		link_text += " {link.url}"
		link_text

	def parse_url url
		throw "invalid url" if url === null
		let get_url = do |s|
			let url = new URL s
			throw _ unless (url.host and url.href)
			url
		try
			return get_url url
		try
			return get_url "https://{url}"
		throw "invalid url"

	def get_pretty_date
		Date!.toString!.split(" ").slice(0, 4).join(" ")

	get selected_link
		state.sorted_links[state.link_selection_index]

	def set_link_selection_index index
		state.link_selection_index = index

	def increment_link_selection_index
		set_link_selection_index Math.min(state.sorted_links.length - 1, state.link_selection_index + 1)

	def decrement_link_selection_index
		set_link_selection_index Math.max(0, state.link_selection_index - 1)

	def navigate link
		await increment_link_frequency link
		window.location.href = link.url

	get math_result
		try
			let result = Number(eval_math state.query)
			throw _ if isNaN result
			throw _ if result.toString! is state.query.trim!
			result
		catch
			no

	def handle_cut e
		return unless e.target.selectionStart == e.target.selectionEnd
		let s = math_result
		s ||= state.query
		await window.navigator.clipboard.writeText(s)
		state.query = ''
		sort_links!

	def handle_add_link
		state.loading = yes
		try
			await add_link state.query
			state.query = ''
			sort_links!
		catch e
			err "adding link", e
		state.loading = no

	def handle_click_link
		let link = selected_link
		if link.is_bang
			state.query = ''
			state.active_bang = link
		else
			navigate link

	get bang
		state.active_bang or config.data.default_bang

	get encoded_bang_query
		"{bang.url}{window.encodeURIComponent(state.query)}"

	get encoded_bang_query_nourl
		"{window.encodeURIComponent(state.query)}"

	def update_history bang
		let text
		if state.bang_selection_index > -1
			text = sorted_bang_history.splice(state.bang_selection_index, 1)[0]
		text ||= state.query.trim!
		return unless text
		let i = bang.history.indexOf(text)
		if i > -1
			bang.history.splice(i, 1)
		bang.history.unshift text
		try
			await put_link bang
		catch e
			err "updating bang history", e

	def delete_bang_history_item
		let text = sorted_bang_history[state.bang_selection_index]
		return unless text
		let i = bang.history.indexOf(text)
		return unless i > -1
		bang.history.splice(i, 1)
		try
			await put_link bang
			state.bang_selection_index = Math.min state.bang_selection_index, sorted_bang_history.length - 1
		catch e
			err "updating bang history", e

	def handle_bang
		return if state.loading
		if state.bang_selection_index > -1
			state.query = sorted_bang_history[state.bang_selection_index]
			state.bang_selection_index = -1
			return
		await increment_link_frequency bang
		await update_history bang
		window.location.href = encoded_bang_query

	def unset_active_bang
		state.active_bang = no
		sort_links!

	def increment_bang_selection_index
		state.bang_selection_index = Math.min(sorted_bang_history.length - 1, state.bang_selection_index + 1)

	def decrement_bang_selection_index
		state.bang_selection_index = Math.max(-1, state.bang_selection_index - 1)

	get sorted_bang_history
		fzi.sort state.query, bang.history

	def delete_bang_history
		bang.history = []
		try
			await put_link bang
			state.bang_selection_index = -1
		catch e
			err "deleting bang history", e
		config.data.default_bang.history = []
		config.save!

	def delete_all_bang_history
		return unless window.confirm "Are you sure you want to delete all bang history?"
		try
			await db.links.toCollection!.modify! do |link| link.history = []
			await reload_db!
		catch e
			err "deleting some link histories", e
		imba.commit!