<!--
	DrCr: Web-based double-entry bookkeeping framework
	Copyright (C) 2022–2024  Lee Yingtong Li (RunasSudo)
	
	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU Affero General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Affero General Public License for more details.
	
	You should have received a copy of the GNU Affero General Public License
	along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->

<template>
	<h1 class="page-heading">
		Statement lines
	</h1>
	
	<div class="my-2 py-2 flex bg-white sticky top-0">
		<div class="grow flex gap-x-2 items-baseline">
			<!--<button class="btn-secondary text-emerald-700 ring-emerald-600">
				Reconcile selected as transfer
			</button>-->
			<RouterLink :to="{ name: 'import-statement' }" class="btn-secondary">
				Import statement
			</RouterLink>
			<!--<a href="{{ url_for('statement_lines', **dict(request.args, unclassified=1)) }}" class="btn-secondary">
				Show only unclassified lines
			</a>-->
		</div>
	</div>
	
	<div id="statement-line-list" class="max-h-[100vh] overflow-y-scroll wk-aa">
		<table class="min-w-full">
			<thead>
				<tr class="border-b border-gray-300">
					<th></th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold text-start">Source account</th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold lg:w-[12ex] text-start">Date</th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold text-start">Description</th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold text-start">Charged to</th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold lg:w-[12ex] text-end">Dr</th>
					<th class="py-0.5 px-1 align-bottom text-gray-900 font-semibold lg:w-[12ex] text-end">Cr</th>
					<th class="py-0.5 pl-1 align-bottom text-gray-900 font-semibold text-end">Balance</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td></td>
					<td class="py-0.5 px-1" colspan="7">Loading data…</td>
				</tr>
			</tbody>
		</table>
	</div>
</template>

<script setup lang="ts">
	import Clusterize from 'clusterize.js';
	
	import dayjs from 'dayjs';
	
	import { CheckIcon, PencilIcon } from '@heroicons/vue/24/outline';
	
	import { onUnmounted, ref, watch } from 'vue';
	
	import { db } from '../db.ts';
	import { renderComponent } from '../webutil.ts';
	import { ppWithCommodity } from '../display.ts';
	
	interface StatementLine {
		id: number,
		source_account: string,
		dt: string,
		description: string,
		quantity: number,
		balance: number | null,
		commodity: string,
		transaction_id: number,
		posting_accounts: string[]
	}
	
	const statementLines = ref([] as StatementLine[]);
	let clusterize: Clusterize | null = null;
	
	async function load() {
		const session = await db.load();
		
		const joinedStatementLines: any[] = await session.select(
			`SELECT statement_lines.*, p2.transaction_id, p2.account AS posting_account
			FROM statement_lines
			LEFT JOIN statement_line_reconciliations ON statement_lines.id = statement_line_reconciliations.statement_line_id
			LEFT JOIN postings ON statement_line_reconciliations.posting_id = postings.id
			LEFT JOIN transactions ON postings.transaction_id = transactions.id
			LEFT JOIN postings p2 ON transactions.id = p2.transaction_id
			ORDER BY statement_lines.dt DESC, statement_lines.id DESC, p2.id`
		);
		
		// Unflatten statement lines
		const newStatementLines: StatementLine[] = [];
		
		for (const joinedStatementLine of joinedStatementLines) {
			if (newStatementLines.length === 0 || newStatementLines.at(-1)!.id !== joinedStatementLine.id) {
				newStatementLines.push({
					id: joinedStatementLine.id,
					source_account: joinedStatementLine.source_account,
					dt: joinedStatementLine.dt,
					description: joinedStatementLine.description,
					quantity: joinedStatementLine.quantity,
					balance: joinedStatementLine.balance,
					commodity: joinedStatementLine.commodity,
					transaction_id: joinedStatementLine.transaction_id,
					posting_accounts: []
				});
			}
			if (joinedStatementLine.posting_account !== null) {
				newStatementLines.at(-1)!.posting_accounts.push(joinedStatementLine.posting_account);
			}
		}
		
		statementLines.value = newStatementLines;
	}
	
	// TODO: Could probably avoid polluting global scope by using clusterize clusterChanged callback
	(window as any).showClassifyLinePanel = function(el: HTMLAnchorElement) {
		const CheckIconHTML = renderComponent(CheckIcon, { 'class': 'w-5 h-5' });
		
		const td = el.closest('td')!;
		td.className = 'relative';  // CSS trickery so as to not expand the height of the tr
		td.innerHTML =
			`<div class="flex items-stretch absolute top-[-4px]">
				<input type="text" class="bordered-field">
				<button type="button" class="relative -ml-px inline-flex items-center gap-x-1.5 px-3 py-1 text-gray-800 shadow-sm ring-1 ring-inset ring-gray-400 bg-white hover:bg-gray-50">${ CheckIconHTML }</button>
			</div>`;
		
		td.querySelector('input')!.addEventListener('keydown', async function(event: KeyboardEvent) {
			if (event.key === 'Enter') {
				await onLineClassified(event);
			}
		})
		td.querySelector('button')!.addEventListener('click', onLineClassified);
		
		td.querySelector('input')!.focus();
		
		return false;
	};
	
	async function onLineClassified(event: Event) {
		// Callback when clicking OK or pressing enter to classify a statement line
		if ((event.target as HTMLInputElement).disabled) {
			return;
		}
		
		const td = (event.target as Element).closest('td')!;
		const tr = td.closest('tr')!;
		const lineId = parseInt(tr.dataset.lineId!);
		const chargeAccount = (td.querySelector('input')! as HTMLInputElement).value;
		
		if (!chargeAccount) {
			return;
		}
		
		// Disable further submissions
		td.querySelector('input')!.disabled = true;
		td.querySelector('button')!.disabled = true;
		
		const statementLine = statementLines.value.find((l) => l.id === lineId)!;
		
		// Insert transaction and statement line reconciliation atomically
		const session = await db.load();
		const dbTransaction = await session.begin();
		
		// Insert transaction
		const transactionResult = await dbTransaction.execute(
			`INSERT INTO transactions (dt, description)
			VALUES ($1, $2)`,
			[statementLine.dt, statementLine.description]
		);
		const transactionId = transactionResult.lastInsertId;
		
		// Insert posting for this account
		const accountPostingResult = await dbTransaction.execute(
			`INSERT INTO postings (transaction_id, description, account, quantity, commodity, running_balance)
			VALUES ($1, NULL, $2, $3, $4, NULL)`,
			[transactionId, statementLine.source_account, statementLine.quantity, statementLine.commodity]
		);
		const accountPostingId = accountPostingResult.lastInsertId;
		
		// Insert posting for the charge account - no need to remember this ID
		await dbTransaction.execute(
			`INSERT INTO postings (transaction_id, description, account, quantity, commodity, running_balance)
			VALUES ($1, NULL, $2, $3, $4, NULL)`,
			[transactionId, chargeAccount, -statementLine.quantity, statementLine.commodity]
		);
		
		// Insert statement line reconciliation
		await dbTransaction.execute(
			`INSERT INTO statement_line_reconciliations (statement_line_id, posting_id)
			VALUES ($1, $2)`,
			[statementLine.id, accountPostingId]
		);
		
		dbTransaction.commit();
		
		// Reload transactions and re-render the table
		await load();
	}
	
	function renderTable() {
		const PencilIconHTML = renderComponent(PencilIcon, { 'class': 'w-4 h-4 inline align-middle -mt-0.5' });  // Pre-render the pencil icon
		const rows = [];
		
		for (const line of statementLines.value) {
			let reconciliationCell;
			if (line.posting_accounts.length === 0) {
				// Unreconciled
				reconciliationCell =
					`<a href="#" class="classify-link text-red-500 hover:text-red-600 hover:underline" onclick="return showClassifyLinePanel(this);">Unclassified</a>`;
			} else if (line.posting_accounts.length === 2) {
				// Simple reconciliation
				const otherAccount = line.posting_accounts.find((a) => a !== line.source_account);
				reconciliationCell =
					`<span>${ otherAccount }</span>
					<a href="/journal/edit-transaction/${ line.transaction_id }" class="text-gray-500 hover:text-gray-700" onclick="return openLinkInNewWindow(this);">${ PencilIconHTML }</a>`;
			} else {
				// Complex reconciliation
				reconciliationCell =
					`<i>(Complex)</i>
					<a href="/journal/edit-transaction/${ line.transaction_id }" class="text-gray-500 hover:text-gray-700" onclick="return openLinkInNewWindow(this);">${ PencilIconHTML }</a>`;
			}
			
			rows.push(
				`<tr data-line-id="${ line.id }">
					<td class="py-0.5 pr-1 align-baseline"><input class="checkbox-primary" type="checkbox" name="sel-line-id" value="${ line.id }"></td>
					<td class="py-0.5 px-1 align-baseline text-gray-900"><a href="#" class="hover:text-blue-700 hover:underline">${ line.source_account }</a></td>
					<td class="py-0.5 px-1 align-baseline text-gray-900 lg:w-[12ex]">${ dayjs(line.dt).format('YYYY-MM-DD') }</td>
					<td class="py-0.5 px-1 align-baseline text-gray-900">${ line.description }</td>
					<td class="charge-account py-0.5 px-1 align-baseline text-gray-900">${ reconciliationCell }</td>
					<td class="py-0.5 px-1 align-baseline text-gray-900 lg:w-[12ex] text-end">${ line.quantity >= 0 ? ppWithCommodity(line.quantity, line.commodity) : '' }</td>
					<td class="py-0.5 px-1 align-baseline text-gray-900 lg:w-[12ex] text-end">${ line.quantity < 0 ? ppWithCommodity(-line.quantity, line.commodity) : '' }</td>
					<td class="py-0.5 pl-1 align-baseline text-gray-900 text-end">${ line.balance ?? '' }</td>
				</tr>`
			)
		}
		
		if (clusterize === null) {
			clusterize = new Clusterize({
				'rows': rows,
				scrollElem: document.getElementById('statement-line-list')!,
				contentElem: document.querySelector('#statement-line-list tbody')!
			});
		} else {
			clusterize.update(rows);
		}
	}
	
	watch(statementLines, renderTable);
	
	load();
	
	onUnmounted(() => {
		if (clusterize !== null) {
			clusterize.destroy();
		}
	});
</script>
