'''
convert.py

brython frontend logic for convert.html.  sends files to /convert one at a time (even for
a multi-file batch) so the page can update as each result lands instead of showing a blank
screen while the whole batch churns.  talks to the api in d2server.py.

note: this runs in the browser, not on the server - universals.py isn't loaded here, so this
uses plain True / False / None rather than the lowercase aliases used server-side.
'''

from browser import ajax
from browser import html
from browser import window
from browser import document

import utils as u
from utils import *

# -------------------- globals --------------------

FLAGINFO = [
	( 'addwp'    , 'add all waypoints' ) ,
	( 'dointros' , 'trigger quest intros' ) ,
]

g = u.struct ()

g.queue      = []          # File objects still waiting to be sent
g.jobid      = ''          # set from the first server response, reused for rest of the batch
g.donecount  = 0
g.totalcount = 0

# -------------------- helper funcs --------------------

def setbusy (busy) :
	'''enable/disable the form while a batch is in flight.'''

	document [ 'submitbtn' ].disabled = busy
	document [ 'fileinput' ].disabled = busy

# ------------------------------------------------

def showtime (isostr) :
	'''format ISO UTC timestamp string to local browser time string "27 Aug 3:39 pm".'''

	if not isostr : return ''

	# append Z if no timezone offset present to force UTC parsing in JS Date
	s = isostr if (isostr.endswith ('Z') or '+' in isostr) else f'{ isostr }Z'
	d = window.Date.new (s)

	if window.isNaN (d.getTime ()) :
		return isostr

	day     = d.getDate ()
	months  = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]
	month   = months [ d.getMonth () ]
	hours   = d.getHours ()
	minutes = d.getMinutes ()

	ampm    = 'pm' if hours >= 12 else 'am'
	hours12 = hours % 12
	if hours12 == 0 : hours12 = 12

	min_str = f'0{ minutes }' if minutes < 10 else str (minutes)

	return f'{ day } { month } { hours12 }:{ min_str } { ampm }'

# ------------------------------------------------

def addresultrow (records) :
	'''insert result record(s) at top of table, ordering cells by header <th> data-field.'''

	if isinstance (records, dict) :
		records = [ records ]

	if not records :
		return

	# log results to console

	th_list = document.select ('#resultsbox table thead th')
	fields  = [ th.getAttribute ('data-field') or '' for th in th_list ]
	tbody   = document [ 'resultsbody' ]

	for rec in records :

		text = ' ; ' .join ([ f'{k} = {v}' for k,v in rec.items () ]) 
		log (f'record = { text }')

		row = html.TR ()

		for field in fields :
			cell = html.TD ()
			val  = rec.get (field, '')

			if field == 'status' :
				cell.text = str (val)
				cell.classList.add (f'status-{ val }')

			elif field == 'time' :
				cell.text = showtime (str (val) if val else '')

			elif field.endswith ('url') :
				desc = rec.get ('infile', '') if val else ''
				link = html.A (desc)
				if val :  link.href = val
				cell <= html.A (desc, href = val)

			else :
				cell.text = str (val) if val is not None else ''

			row <= cell

		if tbody.firstChild :
			tbody.insertBefore (row, tbody.firstChild)
		else :
			tbody <= row

# ------------------------------------------------

def getflags (form_id = 'uploadform') :
	'read checked flag checkboxes, return a comma-separated string for the form field.'

	form = doc [form_id]
	flags = {}

	# --- read values from form
	
	for elem in form.elements:

		name = elem.name
		
		# Skip elements without a name, buttons, or file inputs
		if not name or elem.type in ('file', 'submit', 'button', 'reset') :
			continue
			
		# Only include checked items for radio buttons & checkboxes
		if elem.type in ('radio', 'checkbox') :
			if elem.checked :
				flags [name] = elem.value
		else :
			# Handles standard text, number, and <select> dropdown values
			flags [name] = elem.value

	# --- convert dest version flags
	# d2swag expects a given format : <major>_<minor>?

	version = flags.pop ('dst_fmt', none)
	season  = flags.pop ('dst_season', none)

	season = season and int (season) or 0

	if season and version != 'd2' :  version = f'{ version }_s{ season }'

	flags ['dstfmt'] = version
			
	return flags

# -------------------- upload flow --------------------

def sendnext () :
	'''pop the next file off the queue and send it.  called once at batch start and again
	after each result comes back, so files go out one at a time.'''

	if not g.queue :
		setbusy (False)
		document [ 'statusline' ].text = f'done - { str (g.donecount) } of { str (g.totalcount) } converted'
		return

	nextfile = g.queue.pop (0)
	flags = getflags ()

	log (f'sending file : { nextfile }')
	log (f'with flags   : { flags }')

	jsonflags = window.JSON.stringify (flags)

	formdata = window.FormData.new ()
	formdata.append ('file', nextfile)
	#formdata.append ('flags', getselectedflags ())
	formdata.append ('flags', jsonflags)
	formdata.append ('jobid', g.jobid)

	request = ajax.Ajax ()
	request.bind ('complete', onresult)
	request.open ('POST', '/api/convert', True)
	request.send (formdata)

# ------------------------------------------------

def onresult (request) :
	'''handle one /convert response, success or error - update the table, bump the counter,
	and move on to the next queued file.'''

	g.donecount += 1
	document [ 'statusline' ].text = f'converting { g.donecount } of { g.totalcount } ...'

	try :
		data = request.json
	except :
		addresultrow ('(unknown)', 'error', 'server sent back something unreadable', '')
		addresultrow ({ 'infile' : 'unknown', 'status' : 'error', 'message' : 'unknown server response' })
		sendnext ()
		return

	if request.status == 200 :
		if not g.jobid :
			g.jobid = data ['jobid']
			updateurl (g.jobid)

		addresultrow (data)
	else :
		addresultrow ({
			'infile'  : 'unknown' ,
			'status'  : 'error' ,
			'message' : data.get ('detail', 'request failed') ,
		})

	sendnext ()

# ------------------------------------------------

def onsubmit (event) :
	'''submit-button handler - just hands the picked files to startbatch.'''
	event.preventDefault ()
	startbatch (document [ 'fileinput' ].files)

# ------------------------------------------------

def startbatch (filelist) :
	'''common kickoff for a new batch - used by both the submit button and drag/drop.  wipes
	the results table, queues every file, and starts sending them one at a time.'''

	if filelist.length == 0 :
		document [ 'statusline' ].text = 'pick at least one file first'
		return

	for i in range (filelist.length) :  g.queue.append (filelist [ i ])
	g.donecount  = 0
	g.totalcount = filelist.length

	#document [ 'resultsbody' ].clear ()  # DONT CLEAR THIS SHIT !!!

	document [ 'resultsbox' ].style.display = 'block'
	setbusy (True)
	document [ 'statusline' ].text = 'converting 0 of ' + str (g.totalcount) + ' ...'

	sendnext ()

# ------------------------------------------------
def ondragover (event) :
	'''highlight the dropzone while a file is dragged over it.'''
	event.preventDefault ()
	document [ 'dropzone' ].classList.add ('dropzone-active')

# ------------------------------------------------
def ondragleave (event) :
	'''un-highlight the dropzone once the drag leaves it.'''
	event.preventDefault ()
	document [ 'dropzone' ].classList.remove ('dropzone-active')

# ------------------------------------------------
def ondrop (event) :
	'''files dropped on the dropzone - convert with whatever the checkboxes currently say.'''
	event.preventDefault ()
	event.stopPropagation ()
	document [ 'dropzone' ].classList.remove ('dropzone-active')
	startbatch (event.dataTransfer.files)

# ------------------------------------------------

def updateurl (new_jobid) :
	'''update browser query parameters with the jobid while stripping fileinput.'''

	window.location.hash = f'jobid={ new_jobid }'

# ------------------------------------------------

def parsejobid () :
	'''extract jobid from location hash (#jobid=xxx or #xxx) or query params fallback.'''

	raw_hash = window.location.hash.lstrip ('#').strip ()
	if raw_hash :
		if 'jobid=' in raw_hash :
			for item in raw_hash.split ('&') :
				if item.startswith ('jobid=') :
					return item.split ('=', 1) [ 1 ]
		return raw_hash

	params = window.URLSearchParams.new (window.location.search)
	return params.get ('jobid') or ''

# ------------------------------------------------

def fetchjobhistory (job_id) :
	'''fetch existing conversion history for a given jobid.'''

	log (f'fetching job history : { job_id }')

	request = ajax.Ajax ()
	request.bind ('complete', onhistoryresult)
	request.open ('GET', f'/api/job/{ job_id }', True)
	request.send ()

# ------------------------------------------------

def onhistoryresult (request) :
	'''populate the results table when history for a jobid is loaded.'''

	if request.status != 200 :
		document [ 'statusline' ].text = f'failed to load job { g.jobid }'
		return

	try :
		data = request.json
	except :
		document [ 'statusline' ].text = f'corrupted response for job { g.jobid }'
		return

	log (f'got job history : { data }')

	document [ 'resultsbody' ].clear ()
	document [ 'resultsbox' ].style.display = 'block'

	items = data.get ('results', data) if isinstance (data, dict) else data
	if isinstance (items, list) :
		addresultrow (items)
		document [ 'statusline' ].text = f'loaded job { g.jobid } ({ len (items) } files)'

# -------------------- batch download flow --------------------

def onbatchclick (event) :
	'''send a request to /api/makebatch for the active jobid.'''
	event.preventDefault ()

	log (f'sending batch request')

	if not g.jobid :
		document [ 'statusline' ].text = 'no files for batch download yet'
		return

	document [ 'statusline' ].text = 'generating batch zip ...'

	request = ajax.Ajax ()
	request.bind ('complete', onbatchresult)
	request.open ('GET', f'/api/makebatch/{ g.jobid }', True)
	request.send ()

# ------------------------------------------------

def onbatchresult (request) :
	'''handle /api/makebatch response: add record to table and trigger browser download.'''

	log (f'got batch response')
	if request.status != 200 :
		try :
			data = request.json
			msg  = data.get ('detail', 'failed to make batch zip')
		except :
			msg  = 'failed to make batch zip'

		document [ 'statusline' ].text = msg
		return

	try :
		data = request.json
	except :
		document [ 'statusline' ].text = 'bad server response for batch request'
		return

	# a) append to table
	addresultrow (data)
	document [ 'statusline' ].text = 'batch zip created'

	# b) trigger seamless download without page navigation
	convurl = data.get ('convurl', '')
	if convurl :
		link = html.A (href = convurl, style = { 'display' : 'none' })
		document <= link
		link.click ()
		link.remove ()

# ------------------------------------------------

def loadfromurl () :
	'''read query string on startup; fetch job history if jobid exists, strip fileinput.'''

	log (f'loading info from url')

	url_jobid = parsejobid ()
	if url_jobid :
		g.jobid = url_jobid
		fetchjobhistory (g.jobid)

# -------------------- wire up --------------------

document [ 'uploadform' ].bind ('submit', onsubmit)
# batch download button
document [ 'batchbtn' ].bind ('click', onbatchclick)

# whole page drag n drop
document.bind ('dragover', ondragover)
document.bind ('dragleave', ondragleave)
document.bind ('drop', ondrop)

# dropzone drag n drop only
#document [ 'dropzone' ].bind ('dragover', ondragover)
#document [ 'dropzone' ].bind ('dragleave', ondragleave)
#document [ 'dropzone' ].bind ('drop', ondrop)

loadfromurl ()

