// Questo file fa parte di MyTown.
// MyTown è software libero: puoi ridistribuirlo e/o modificarlo secondo
// i termini della GNU Affero General Public License come pubblicata dalla
// Free Software Foundation, versione 3.
// Distribuito senza ALCUNA GARANZIA; vedi la GNU AGPL v3 per i dettagli.
// Copyright (C) Kondor Consulting & Services S.r.l.
// SPDX-License-Identifier: AGPL-3.0-or-later

import { Component } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ConfigService } from '../../../providers/config.service';
import { LayoutService } from '../../../providers/layout.service';
import { FileDownloadService } from '../../../providers/fileDownload.service';
import { WorkflowService } from '../../../providers/workflow.service';
import { CompaniesService } from '../../../providers/companies.service';
import { WfActivity, WfCampo, WfComponent, WfSnapshot, WfSnapshotFile, WfUploadedFile } from '../../../models/workflow.model';

/** Campo read-only mostrato nei "dati dei passi precedenti". */
interface DisplayField {
	label: string;
	value: string;
	type: number;
	files: WfUploadedFile[];
	isNote: boolean;
	open?: boolean; // nota: aperta/chiusa
}
interface PrevStep {
	label: string;
	fields: DisplayField[];
}

/**
 * Esecuzione di un'attività di workflow — porta SOLO la modalità "execute" del
 * componente DesignFieldActivity del portale (niente designer).
 *
 * Render: campi compilabili (testo / combo / multiselect) + pulsanti d'azione.
 * Submit: POST { activity, button } come il portale; se il pulsante ha un campo
 * con option_search === 'activity_next' e il backend ritorna un next job, si
 * passa allo step successivo, altrimenti si torna alla lista.
 *
 * Snapshot (dati entità linkata, popolati lato web) mostrato read-only in testa.
 */
@Component({
	selector: 'app-workflow-execute',
	templateUrl: './workflow-execute.page.html',
	styleUrls: ['./workflow-execute.page.scss'],
	standalone: false,
})
export class WorkflowExecutePage {
	wfj_id = '';
	wf_id = -1;
	cmp_id = 0;
	prt_id = 0;
	loading = false;
	submitting = false;

	activity: WfActivity | null = null;
	snapshot: WfSnapshot | null = null;

	// componenti suddivisi per il render
	inputComponents: WfComponent[] = [];
	buttons: WfComponent[] = [];

	// dati dei passi precedenti (es. la Richiesta), mostrati read-only quando l'attività
	// è configurata con un componente "view precedenti" (campo option_search pre_activity/activityview)
	previousSteps: PrevStep[] = [];

	// rl_id dei ruoli dell'utente (per decidere quali step sono eseguibili da app)
	private userRoleIds = new Set<number>();

	constructor(
		private route: ActivatedRoute,
		private router: Router,
		private config: ConfigService,
		public layout: LayoutService,
		private wf: WorkflowService,
		private fileDownload: FileDownloadService,
		private companies: CompaniesService,
	) {}

	ionViewWillEnter() {
		this.resolveIds();
		const pm = this.route.snapshot.paramMap;
		this.wfj_id = pm.get('wfj_id') || '';
		const startWfId = pm.get('wf_id');
		this.layout.setPageConfig({
			title: 'Esegui attività',
			showBack: true,
			backButtonHref: `/portale/${this.prt_id}/company/${this.cmp_id}/service/workflow`,
		});
		this.loadUserRoles();
		if (startWfId) {
			// modalità start: avvio di un nuovo workflow
			this.startActivity(+startWfId, 0);
		} else {
			this.loadJob();
		}
	}

	/** Carica gli rl_id dei ruoli dell'utente (per il gating degli step). */
	private loadUserRoles() {
		this.companies.getAssignedCompanyRoles(this.config.getUserID()).subscribe({
			next: (roles: any[]) => {
				this.userRoleIds = new Set((roles || []).map((r) => Number(r.rl_id)).filter((n) => !isNaN(n)));
			},
			error: () => {
				this.userRoleIds = new Set();
			},
		});
	}

	/** rl_id abilitati su un'attività (campi con option_search === 'roles'). */
	private activityRoleIds(activity: WfActivity | null): number[] {
		if (!activity) return [];
		const ids: number[] = [];
		const scan = (campi?: WfCampo[]) => {
			for (const f of campi || []) {
				if (f.option_search === 'roles') {
					for (const v of f.value_list || []) {
						const n = Number(v);
						if (!isNaN(n)) ids.push(n);
					}
				}
			}
		};
		scan(activity.campi);
		for (const c of activity.components || []) scan(c.campi);
		return ids;
	}

	/**
	 * Lo step è eseguibile da app solo se ha almeno un ruolo che l'utente possiede.
	 * Se non ha ruoli definiti => non è uno step da app (lo gestisce l'azienda sul web).
	 */
	private userCanExecute(activity: WfActivity | null): boolean {
		const ids = this.activityRoleIds(activity);
		if (!ids.length) return false;
		return ids.some((id) => this.userRoleIds.has(id));
	}

	/** Modalità start: carica la prima attività del workflow (activity/start). */
	startActivity(wf_id: number, wf_state = 0) {
		this.loading = true;
		this.wf_id = wf_id;
		this.wfj_id = ''; // nessun job ancora: lo crea il backend all'exec
		this.wf.getStartActivity(wf_id, wf_state).subscribe({
			next: (data: any) => {
				try {
					this.activity = data?.wfa_jobparams ? JSON.parse(data.wfa_jobparams) : null;
				} catch {
					this.activity = null;
				}
				this.snapshot = null;
				this.previousSteps = []; // in avvio non ci sono passi precedenti
				this.splitComponents();
				this.loading = false;
			},
			error: (e) => {
				this.config.presentToastError(typeof e === 'string' ? e : 'Errore nell’avvio del workflow');
				this.loading = false;
			},
		});
	}

	private resolveIds() {
		let r: ActivatedRoute | null = this.route;
		while (r) {
			const pm = r.snapshot.paramMap;
			if (pm.get('cmp_id')) this.cmp_id = +(pm.get('cmp_id') as string);
			if (pm.get('prt_id')) this.prt_id = +(pm.get('prt_id') as string);
			r = r.parent;
		}
	}

	loadJob() {
		if (!this.wfj_id) return;
		this.loading = true;
		this.wf.getJob(this.wfj_id).subscribe({
			next: (job) => {
				const { activity, snapshot, activityPre } = this.wf.parseJob(job);
				this.activity = activity;
				this.snapshot = snapshot;
				this.wf_id = (job?.wf_id as number) || -1;
				this.splitComponents();
				this.buildPreviousSteps(activityPre);
				this.loading = false;
			},
			error: (e) => {
				this.config.presentToastError(typeof e === 'string' ? e : 'Errore nel caricamento dell’attività');
				this.loading = false;
			},
		});
	}

	/** Separa i componenti pulsante da quelli con campi compilabili (esclude i "view precedenti"). */
	private splitComponents() {
		this.inputComponents = [];
		this.buttons = [];
		const comps = this.activity?.components || [];
		for (const c of comps) {
			if ((c.cm_module_name || '').toLowerCase().startsWith('button')) {
				this.buttons.push(c);
			} else if (this.isViewComponent(c)) {
				continue; // gestito come "Dati attività precedente"
			} else if (this.visibleCampi(c).length > 0) {
				this.inputComponents.push(c);
			}
		}
	}

	/** True se il componente è un "view precedenti" (campo pre_activity/activityview). */
	private isViewComponent(c: WfComponent): boolean {
		return (c.campi || []).some((f) => f.option_search === 'pre_activity' || f.option_search === 'activityview' || f.option_search === 'activity');
	}

	/** Indici delle attività precedenti da mostrare (null = nessuna config di view). */
	private viewIndices(activity: WfActivity | null): number[] | null {
		if (!activity) return null;
		let found = false;
		const idx: number[] = [];
		const scan = (campi?: WfCampo[]) => {
			for (const f of campi || []) {
				if (f.option_search === 'pre_activity' || f.option_search === 'activityview' || f.option_search === 'activity') {
					found = true;
					for (const v of f.value_list || []) {
						const n = Number(v);
						if (!isNaN(n)) idx.push(n);
					}
				}
			}
		};
		scan(activity.campi);
		for (const c of activity.components || []) scan(c.campi);
		return found ? idx : null;
	}

	/** Costruisce i "dati precedenti" da activity_pre, secondo la config dell'attività. */
	private buildPreviousSteps(activityPre: WfActivity[]) {
		this.previousSteps = [];
		const indices = this.viewIndices(this.activity);
		if (indices === null) return; // l'attività non è configurata per mostrare i precedenti
		for (const act of activityPre || []) {
			const i = Number(act?.index);
			if (indices.length && !indices.includes(i)) continue; // filtra per indici configurati
			const fields = this.displayFieldsOf(act);
			if (fields.length) this.previousSteps.push({ label: act?.label || 'Dati precedenti', fields });
		}
	}

	/** Campi read-only mostrabili di un'attività (valori inseriti). */
	private displayFieldsOf(activity: WfActivity | null): DisplayField[] {
		if (!activity) return [];
		const skip = ['roles', 'pre_activity', 'activityview', 'activity', 'activity_next', 'link', 'instance_name'];
		const out: DisplayField[] = [];
		const push = (campi?: WfCampo[]) => {
			for (const f of campi || []) {
				if (f.visible === false) continue;
				if (f.settings && f.type !== 9) continue;
				if (f.option_search && skip.includes(f.option_search)) continue;
				const files = f.type === 8 ? f.file || [] : [];
				const value = this.displayValue(f);
				if (f.type !== 9 && files.length === 0 && !value) continue;
				out.push({ label: f.label, value, type: f.type, files, isNote: f.type === 9 });
			}
		};
		push(activity.campi);
		for (const c of activity.components || []) {
			if ((c.cm_module_name || '').toLowerCase().startsWith('button')) continue;
			push(c.campi);
		}
		return out;
	}

	private displayValue(f: WfCampo): string {
		if (f.type === 7) return (f.value_list || []).map((v) => this.optionText(f, v)).join(', ');
		if (f.type === 3) return this.optionText(f, f.value);
		return f.value || '';
	}

	private optionText(f: WfCampo, val: any): string {
		const opt = (f.option || []).find((o) => String(o.value) === String(val));
		return opt ? opt.text : String(val ?? '');
	}

	/** Apre un allegato (type 8) di un passo precedente. */
	openPrevFile(file: WfUploadedFile) {
		const url = this.wf.uploadedFileUrl(file.repositoryName);
		if (!url) {
			this.config.presentToastError('File non disponibile');
			return;
		}
		this.fileDownload.downloadAndOpenFile(url, file.fileOrigName || 'file');
	}

	prevFileIcon(file: WfUploadedFile): string {
		return this.config.getFileIcon(file.fileOrigName || '');
	}

	/**
	 * Campi mostrabili di un componente (esclude i campi di configurazione e i nascosti).
	 * Le note/istruzioni (type 9) sono sempre mostrate anche se marcate settings.
	 */
	visibleCampi(c: WfComponent): WfCampo[] {
		return (c.campi || []).filter((f) => f.visible !== false && (!f.settings || f.type === 9));
	}

	/** Mappa il type del campo sul tipo dell'<input> HTML (per i tipi base). */
	inputType(f: WfCampo): string {
		switch (f.type) {
			case 0:
				return 'number';
			case 2:
				return 'date';
			case 4:
				return 'email';
			case 5:
				return 'tel';
			case 6:
				return 'password';
			case 14:
				return 'datetime-local';
			default:
				return 'text';
		}
	}

	buttonLabel(b: WfComponent): string {
		// label dal primo campo testuale o dalla descrizione del componente
		const labelled = (b.campi || []).find((f) => f.label || f.value);
		return b.cm_description || labelled?.label || labelled?.value || 'Conferma';
	}

	private hasActivityNext(b: WfComponent): boolean {
		return (b.campi || []).some((f) => f.option_search === 'activity_next');
	}

	/** Esegue l'attività con il pulsante selezionato (replica executeButton del portale). */
	exec(button: WfComponent) {
		if (!this.activity || this.submitting) return;

		const missing = this.validateRequired();
		if (missing) {
			this.config.presentToastWarning('Compila i campi obbligatori: ' + missing);
			return;
		}

		this.submitting = true;
		this.wf.execActivity(this.wf_id, this.wfj_id, this.activity, button).subscribe({
			next: (res: any) => {
				this.submitting = false;
				this.config.presentToastSuccess('Operazione eseguita con successo');
				if (res?.res && this.hasActivityNext(button)) {
					// avanza in-app SOLO se il prossimo step è di competenza dell'utente
					this.advanceOrFinish(String(res.res));
				} else {
					this.goBackToList();
				}
			},
			error: (e) => {
				this.submitting = false;
				this.config.presentToastError(typeof e === 'string' ? e : 'Errore durante l’esecuzione');
			},
		});
	}

	private validateRequired(): string {
		const missing: string[] = [];
		for (const c of this.inputComponents) {
			for (const f of this.visibleCampi(c)) {
				if (!f.required) continue;
				let empty: boolean;
				if (f.type === 7) empty = !(f.value_list && f.value_list.length);
				else if (f.type === 8) empty = !(f.file && f.file.length);
				else empty = !f.value;
				if (empty) missing.push(f.label || f.name);
			}
		}
		return missing.join(', ');
	}

	// ─── allegati (campo type 8) ───────────────────────────────────
	fileInputId(c: WfComponent, f: WfCampo): string {
		return `wf-file-${c.cm_id ?? ''}-${f.name}`;
	}

	triggerFile(c: WfComponent, f: WfCampo) {
		document.getElementById(this.fileInputId(c, f))?.click();
	}

	onFileSelected(event: any, f: WfCampo) {
		const input = event.target as HTMLInputElement;
		if (!input.files || input.files.length === 0) return;
		if (!f.file) f.file = [];
		const files = Array.from(input.files);
		for (const file of files) {
			this.wf.uploadTempFile(file).subscribe({
				next: (repositoryName) => {
					if (repositoryName) f.file!.push({ fileOrigName: file.name, repositoryName });
				},
				error: () => this.config.presentToastError('Errore caricamento di ' + file.name),
			});
		}
		input.value = '';
	}

	removeUploaded(f: WfCampo, index: number) {
		f.file?.splice(index, 1);
	}

	/**
	 * Dopo l'esecuzione: carica il prossimo job e prosegue in-app solo se
	 * l'utente ha un ruolo abilitato su quell'attività; altrimenti chiude
	 * (il passaggio successivo lo gestisce l'azienda sul web, resta nello storico).
	 */
	private advanceOrFinish(nextId: string) {
		this.wf.getJob(nextId).subscribe({
			next: (job) => {
				const { activity } = this.wf.parseJob(job);
				if (this.userCanExecute(activity)) {
					this.router.navigate([...this.workflowBase(), 'execute', nextId]).then(() => {
						this.wfj_id = nextId;
						this.loadJob();
					});
				} else {
					this.config.presentToastSuccess('Inviato. I passaggi successivi sono gestiti dall’azienda.');
					this.goBackToList();
				}
			},
			error: () => this.goBackToList(),
		});
	}

	/** Path assoluto della sezione workflow di questa azienda. */
	private workflowBase(): any[] {
		return ['/portale', this.prt_id, 'company', this.cmp_id, 'service', 'workflow'];
	}

	private goBackToList() {
		this.router.navigate(this.workflowBase());
	}

	// ─── Snapshot / file documentali ───────────────────────────────
	openFile(f: WfSnapshotFile) {
		if (!f?.drfl_id) return;
		this.wf.getFileDownloadUrl(f.drfl_id).subscribe({
			next: (url) => {
				if (!url) {
					this.config.presentToastError('File non disponibile');
					return;
				}
				this.fileDownload.downloadAndOpenFile(url, f.filename || 'file', f.mime);
			},
			error: () => this.config.presentToastError('Errore download del file'),
		});
	}

	fileIcon(f: WfSnapshotFile): string {
		return this.config.getFileIcon(f.filename || '', f.mime);
	}
}
