/**
 * Planeación — Planner v1
 *
 * Enfoque: timeline lineal + cola sort_order + ledger semana anterior.
 * - Una sola pasada, un solo cursor global.
 * - Semana anterior solo registra minutos consumidos ANTES del lunes visible.
 * - Sin precolocados ni reordenamientos ocultos.
 */
(function (global) {
	'use strict';

	var PLANNER_VERSION = 1;
	var PLANEACION_TIMEZONE = 'America/Bogota';

	function getTodayYmdInTimezone(timeZone) {
		try {
			return new Intl.DateTimeFormat('en-CA', {
				timeZone: timeZone || PLANEACION_TIMEZONE,
				year: 'numeric',
				month: '2-digit',
				day: '2-digit'
			}).format(new Date());
		} catch (error) {
			return '';
		}
	}

	function currentMinutesInTimezone(timeZone) {
		try {
			var parts = new Intl.DateTimeFormat('en-GB', {
				timeZone: timeZone || PLANEACION_TIMEZONE,
				hour: '2-digit',
				minute: '2-digit',
				hour12: false
			}).formatToParts(new Date());
			var hour = 0;
			var minute = 0;

			parts.forEach(function (part) {
				if (part.type === 'hour') {
					hour = parseInt(part.value, 10);
				}
				if (part.type === 'minute') {
					minute = parseInt(part.value, 10);
				}
			});

			return (hour * 60) + minute;
		} catch (error) {
			var now = new Date();
			return (now.getHours() * 60) + now.getMinutes();
		}
	}

	function weekStartFromDate(dateStr, fallbackYmd) {
		if (!dateStr) {
			dateStr = getTodayYmdInTimezone(PLANEACION_TIMEZONE) || fallbackYmd || '';
		}

		var parts = String(dateStr).split('-');
		if (parts.length !== 3) {
			dateStr = getTodayYmdInTimezone(PLANEACION_TIMEZONE) || fallbackYmd || '';
			parts = String(dateStr).split('-');
			if (parts.length !== 3) {
				return fallbackYmd || '';
			}
		}

		var date = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
		var day = date.getDay();
		var diff = day === 0 ? -6 : 1 - day;
		date.setDate(date.getDate() + diff);

		return date.getFullYear() + '-' +
			String(date.getMonth() + 1).padStart(2, '0') + '-' +
			String(date.getDate()).padStart(2, '0');
	}

	function taskKey(task) {
		var op = String(task.op || '').trim().toLowerCase();
		if (op && op !== 'sin op') {
			return 'op:' + op;
		}
		return 'id:' + task.id;
	}

	function segMinutes(seg) {
		var m = Number(seg.minutes);
		if (Number.isFinite(m) && m > 0) {
			return m;
		}
		return Math.max(0, (seg.end_minutes || 0) - (seg.start_minutes || 0));
	}

	function sumBeforeDate(segments, cutoff) {
		if (!cutoff) {
			return 0;
		}
		return (segments || []).reduce(function (t, s) {
			return (s.date && s.date < cutoff) ? t + segMinutes(s) : t;
		}, 0);
	}

	function dateSet(days) {
		var set = {};
		(days || []).forEach(function (d) {
			if (d.date) {
				set[d.date] = true;
			}
		});
		return set;
	}

	function findDayMeta(days, dateStr) {
		for (var i = 0; i < (days || []).length; i++) {
			if (days[i].date === dateStr) {
				return days[i];
			}
		}
		return null;
	}

	function latestSegmentEnd(scheduled) {
		var best = null;
		var bestT = null;
		(scheduled || []).forEach(function (task) {
			(task.schedule_segments || []).forEach(function (seg) {
				var t = ((parseInt(seg.week_offset, 10) || 0) * 7 * 1440) +
					(parseInt(seg.day, 10) * 1440) + (seg.end_minutes || 0);
				if (bestT === null || t > bestT) {
					bestT = t;
					best = seg;
				}
			});
		});
		return best;
	}

	function createCalendar(deps, semanaInicio, dayHours, days, config, maxWeeks) {
		var normalized = deps.normalizeDayHours(dayHours, null, null, days, config);
		var contexts = deps.buildScheduleWeekContexts(semanaInicio, normalized, days, config, maxWeeks);
		return {
			deps: deps,
			config: config,
			contexts: contexts,
			dayMeta: function (w, d) {
				var ctx = contexts[w];
				if (!ctx || !ctx.days) {
					return null;
				}
				var n = parseInt(d, 10);
				for (var i = 0; i < ctx.days.length; i++) {
					if (parseInt(ctx.days[i].index, 10) === n) {
						return ctx.days[i];
					}
				}
				return null;
			},
			isOff: function (w, d) {
				var ctx = contexts[w];
				return !ctx || deps.isDayNonWorking(d, ctx.dayHours, ctx.days, config);
			},
			hours: function (w, d) {
				var ctx = contexts[w];
				return ctx ? deps.getDayHours(ctx.dayHours, d) : deps.getDayHours(normalized, d);
			},
			firstWorkingDay: function (w) {
				for (var d = 0; d <= 6; d++) {
					if (!this.isOff(w, d)) {
						return d;
					}
				}
				return 0;
			}
		};
	}

	function Timeline(cal, deps, lunchStart, lunchEnd) {
		this.cal = cal;
		this.deps = deps;
		this.lunchStart = lunchStart;
		this.lunchEnd = lunchEnd;
		this.w = 0;
		this.d = 0;
		this.m = null;
		this.ready = false;
	}

	Timeline.prototype.jump = function (w, d, m) {
		this.w = parseInt(w, 10) || 0;
		this.d = parseInt(d, 10) || 0;
		this.m = m;
		this.ready = true;
		this.clamp();
	};

	Timeline.prototype.startAtFirst = function () {
		for (var w = 0; w < this.cal.contexts.length; w++) {
			for (var d = this.cal.firstWorkingDay(w); d <= 6; d++) {
				if (this.cal.isOff(w, d)) {
					continue;
				}
				this.w = w;
				this.d = d;
				this.m = this.cal.hours(w, d).start;
				this.ready = true;
				return true;
			}
		}
		return false;
	};

	Timeline.prototype.clamp = function () {
		if (this.m === null) {
			return;
		}
		if (this.cal.isOff(this.w, this.d)) {
			if (!this.nextDay()) {
				this.m = null;
			}
			return;
		}
		var slot = this.cal.hours(this.w, this.d);
		if (this.m >= slot.end) {
			this.nextDay();
			return;
		}
		if (this.m < slot.start) {
			this.m = slot.start;
		}
		this.m = this.deps.skipLunch(this.m);
	};

	Timeline.prototype.nextDay = function () {
		var nw = this.w;
		var nd = this.d + 1;
		if (nd > 6) {
			nw += 1;
			nd = 0;
		}
		for (var w = nw; w < this.cal.contexts.length; w++) {
			var ds = w === nw ? nd : 0;
			for (var d = ds; d <= 6; d++) {
				if (this.cal.isOff(w, d)) {
					continue;
				}
				this.w = w;
				this.d = d;
				this.m = this.cal.hours(w, d).start;
				return true;
			}
		}
		this.m = null;
		return false;
	};

	Timeline.prototype.fill = function (minutes) {
		var out = [];
		var deps = this.deps;
		var guard = 0;

		while (minutes > 0 && guard < 1000) {
			guard += 1;

			if (!this.ready || this.m === null) {
				if (!this.startAtFirst()) {
					return { segments: out, leftover: minutes };
				}
			}

			this.clamp();
			if (this.m === null) {
				return { segments: out, leftover: minutes };
			}

			var slot = this.cal.hours(this.w, this.d);
			var meta = this.cal.dayMeta(this.w, this.d);
			var start = Math.max(this.m, slot.start);
			start = deps.skipLunch(start);

			if (start >= slot.end) {
				if (!this.nextDay()) {
					return { segments: out, leftover: minutes };
				}
				continue;
			}

			var room = slot.end - start;
			var chunk = Math.min(minutes, room);
			var finish = deps.capFinishBeforeLunch(start, start + chunk);
			chunk = finish - start;

			if (chunk < 1) {
				this.m = (start < this.lunchStart && finish >= this.lunchStart) ? this.lunchEnd : deps.advanceCursor(finish);
				if (this.m >= slot.end && !this.nextDay()) {
					return { segments: out, leftover: minutes };
				}
				continue;
			}

			out.push({
				day: this.d,
				week_offset: this.w,
				date: meta ? meta.date : '',
				start_minutes: start,
				end_minutes: finish,
				minutes: chunk,
				is_continuation: out.length > 0
			});

			minutes -= chunk;
			this.m = finish === this.lunchStart ? this.lunchEnd : deps.advanceCursor(finish);

			if (minutes > 0 && this.m >= slot.end) {
				this.nextDay();
			}
		}

		return { segments: out, leftover: minutes };
	};

	function resolveStartCursor(cal, deps, anchorCal, anchorStart, lastSeg, visibleDays, visibleHours) {
		var visibleDates = dateSet(visibleDays);
		var visibleStart = visibleDays && visibleDays.length ? visibleDays[0].date : null;

		if (lastSeg && anchorCal && anchorStart) {
			var anchorMeta = anchorCal.dayMeta(lastSeg.week_offset || 0, lastSeg.day);
			var cursorDate = anchorMeta ? anchorMeta.date : null;
			if (cursorDate && visibleStart && cursorDate >= visibleStart && visibleDates[cursorDate]) {
				var vis = findDayMeta(visibleDays, cursorDate);
				if (vis) {
					return { w: 0, d: parseInt(vis.index, 10), m: lastSeg.end_minutes };
				}
			}
		}

		for (var d = 0; d <= 6; d++) {
			if (!deps.isDayNonWorking(d, visibleHours, visibleDays, cal.config)) {
				return { w: 0, d: d, m: deps.getDayHours(visibleHours, d).start };
			}
		}
		return { w: 0, d: 0, m: deps.getDayHours(visibleHours, 0).start };
	}

	function finalizeTask(task, segs, deps, now, days) {
		if (!segs.length) {
			return;
		}
		var primary = segs[0];
		for (var i = 0; i < segs.length; i++) {
			if (!segs[i].is_continuation && (parseInt(segs[i].week_offset, 10) || 0) === 0) {
				primary = segs[i];
				break;
			}
		}
		var last = segs[segs.length - 1];
		var pDay = parseInt(primary.day, 10);
		var lastWo = parseInt(last.week_offset, 10) || 0;
		if (Number.isFinite(pDay)) {
			task.day = pDay;
		}
		task.schedule_segments = segs;
		task.start_minutes = primary.start_minutes;
		task.end_minutes = last.end_minutes;
		task.continues_next_day = segs.length > 1 || lastWo > 0 || parseInt(last.day, 10) > pDay;
		task.continues_next_week = lastWo > 0;
		task.exceeds_jornada = task.continues_next_day;
		task.status = deps.resolveTaskStatus(task, last.end_minutes, now, days);
	}

	function applyVisibleWeek(scheduledList, liveTasks, visibleDates, displayDays, priorStart) {
		var visibleEnd = displayDays.length ? displayDays[displayDays.length - 1].date : null;
		var byKey = {};
		var carryover = [];

		(liveTasks || []).forEach(function (t) {
			byKey[taskKey(t)] = t;
			t.schedule_segments = [];
			delete t.continues_next_day;
			delete t.continues_next_week;
			delete t.exceeds_jornada;
			delete t.schedule_overflow_unplaced;
		});

		(scheduledList || []).forEach(function (scheduled) {
			var key = taskKey(scheduled);
			var all = scheduled.schedule_segments || [];
			var visible = all.filter(function (s) {
				return s.date && visibleDates[s.date];
			}).map(function (s) {
				var meta = findDayMeta(displayDays, s.date);
				return meta
					? Object.assign({}, s, { week_offset: 0, day: parseInt(meta.index, 10) })
					: Object.assign({}, s);
			});

			if (!visible.length) {
				return;
			}

			var spills = all.some(function (s) {
				return (parseInt(s.week_offset, 10) || 0) > 0 || (s.date && visibleEnd && s.date > visibleEnd);
			});

			var live = byKey[key];
			if (live) {
				live.schedule_segments = visible;
				var home = visible[0];
				for (var i = 0; i < visible.length; i++) {
					if (!visible[i].is_continuation) {
						home = visible[i];
						break;
					}
				}
				var hd = parseInt(home.day, 10);
				if (Number.isFinite(hd)) {
					live.day = hd;
				}
				live.start_minutes = visible[0].start_minutes;
				live.end_minutes = visible[visible.length - 1].end_minutes;
				live.continues_next_day = visible.length > 1 || parseInt(visible[visible.length - 1].day, 10) > hd;
				live.continues_next_week = spills;
				live.exceeds_jornada = live.continues_next_day || live.continues_next_week;
				live.schedule_overflow_unplaced = scheduled.schedule_overflow_unplaced;
				if (!live.status_manual) {
					live.status = scheduled.status;
				}
			} else {
				var ct = Object.assign({}, scheduled, { schedule_segments: visible, continues_next_week: spills });
				var h = visible[0];
				for (var j = 0; j < visible.length; j++) {
					if (!visible[j].is_continuation) {
						h = visible[j];
						break;
					}
				}
				var hDay = parseInt(h.day, 10);
				if (Number.isFinite(hDay)) {
					ct.day = hDay;
				}
				carryover.push({ task: ct, fromWeekStart: priorStart || '' });
			}
		});

		return { tasks: liveTasks, carryoverTasks: carryover };
	}

	function createPlanner(deps) {
		var lunchStart = deps.LUNCH_START;
		var lunchEnd = deps.LUNCH_END;
		var maxWeeks = deps.MAX_SCHEDULE_WEEKS;

		function compareLegacyDaySort(a, b) {
			var da = parseInt(a.day, 10) || 0;
			var db = parseInt(b.day, 10) || 0;
			if (da !== db) {
				return da - db;
			}
			return (a.sort_order || 0) - (b.sort_order || 0);
		}

		function simulatePriorWeek(priorWeek, config) {
			var pHours = deps.normalizeDayHours(
				priorWeek.day_hours,
				parseInt(priorWeek.jornada_inicio, 10) || 420,
				parseInt(priorWeek.jornada_fin, 10) || 1020,
				priorWeek.days,
				config
			);
			var cal = createCalendar(deps, priorWeek.semana_inicio, pHours, priorWeek.days, config, maxWeeks);
			var tl = new Timeline(cal, deps, lunchStart, lunchEnd);
			tl.startAtFirst();
			var now = deps.currentMinutesNow();
			var out = [];
			var sorted = priorWeek.tasks.slice().sort(compareLegacyDaySort);

			sorted.forEach(function (task) {
				deps.applyDurationToTask(task);
				var mins = Number(task.duration_minutes) || 60;
				if (mins < 15) {
					mins = 15;
				}
				var fill = tl.fill(mins);
				var segs = fill.segments;
				if (fill.leftover > 0) {
					task.schedule_overflow_unplaced = fill.leftover;
				}
				finalizeTask(task, segs, deps, now, priorWeek.days);
				out.push(task);
			});

			return {
				scheduled: out,
				calendar: cal,
				lastSeg: latestSegmentEnd(out)
			};
		}

		function planVisibleWeek(tasks, dayHours, days, config, semanaInicio, options) {
			options = options || {};
			var visibleDates = dateSet(days);
			var visibleStart = days && days.length ? days[0].date : semanaInicio;
			var vHours = deps.normalizeDayHours(dayHours, null, null, days, config);
			var cal = createCalendar(deps, semanaInicio, vHours, days, config, maxWeeks);
			var ledger = {};
			var priorSim = null;
			var anchorCal = null;
			var start = null;

			if (options.priorWeek && options.priorWeek.tasks && options.priorWeek.tasks.length) {
				priorSim = simulatePriorWeek(options.priorWeek, config);
				priorSim.scheduled.forEach(function (pt) {
					ledger[taskKey(pt)] = sumBeforeDate(pt.schedule_segments, visibleStart);
				});
				anchorCal = priorSim.calendar;
				start = resolveStartCursor(
					cal, deps, anchorCal, options.priorWeek.semana_inicio,
					priorSim.lastSeg, days, vHours
				);
			}

			var tl = new Timeline(cal, deps, lunchStart, lunchEnd);
			if (start) {
				tl.jump(start.w, start.d, start.m);
			} else {
				tl.startAtFirst();
			}

			var now = deps.currentMinutesNow();
			var queue = (tasks || []).slice().sort(deps.compareGlobalTaskOrder);
			var placed = [];

			deps.markTasksOnClosedDays(queue, vHours, days, config);

			queue.forEach(function (task) {
				deps.applyDurationToTask(task);
				var total = Number(task.duration_minutes) || 60;
				var done = ledger[taskKey(task)] || 0;
				var mins = Math.max(0, total - done);
				if (mins > 0 && mins < 15) {
					mins = 15;
				}
				if (mins <= 0) {
					return;
				}

				var plannedStart = Number(task.planned_start);
				if (Number.isFinite(plannedStart)) {
					var homeDay = parseInt(task.day, 10);
					if (Number.isFinite(homeDay) && tl.ready) {
						var scheduleDay = homeDay;
						if (cal.isOff(0, homeDay) && deps.findAdjacentWorkingDay) {
							var fallbackDay = deps.findAdjacentWorkingDay(homeDay, 1, vHours, days, config);
							if (fallbackDay !== null) {
								scheduleDay = fallbackDay;
							}
						}
						if (!cal.isOff(0, scheduleDay)) {
							var slot = cal.hours(0, scheduleDay);
							var startAt = deps.skipLunch(Math.max(slot.start, plannedStart));
							if (tl.w > 0 || tl.d > scheduleDay || tl.m === null) {
								tl.jump(0, scheduleDay, startAt);
							} else if (tl.d === scheduleDay && tl.m < startAt) {
								tl.m = startAt;
							}
						}
					}
				}

				var copy = Object.assign({}, task);
				var fill = tl.fill(mins);
				if (fill.leftover > 0) {
					copy.schedule_overflow_unplaced = fill.leftover;
				}
				finalizeTask(copy, fill.segments, deps, now, days);
				placed.push(copy);
			});

			var liveKeys = {};
			(tasks || []).forEach(function (t) {
				liveKeys[taskKey(t)] = true;
			});

			var prevOnly = priorSim
				? priorSim.scheduled.filter(function (t) {
					return !liveKeys[taskKey(t)];
				})
				: [];

			var merged = placed.slice();
			prevOnly.forEach(function (pt) {
				var k = taskKey(pt);
				var exists = merged.some(function (m) {
					return taskKey(m) === k;
				});
				if (!exists) {
					merged.push(pt);
				}
			});

			return applyVisibleWeek(
				merged,
				tasks,
				visibleDates,
				days,
				options.priorWeek ? options.priorWeek.semana_inicio : ''
			);
		}

		return {
			VERSION: PLANNER_VERSION,
			taskSchedulingKey: taskKey,
			findDayMetaByDate: findDayMeta,
			normalizeSegmentForDisplayWeek: function (seg, currentDays) {
				var meta = seg.date ? findDayMeta(currentDays, seg.date) : null;
				if (!meta) {
					return Object.assign({}, seg);
				}
				return Object.assign({}, seg, {
					week_offset: 0,
					day: parseInt(meta.index, 10)
				});
			},
			getWeekDateSet: dateSet,
			filterSegmentsForVisibleWeek: function (segments, visibleDates, displayDays) {
				return (segments || []).filter(function (s) {
					return s.date && visibleDates[s.date];
				}).map(function (s) {
					var meta = findDayMeta(displayDays, s.date);
					return meta
						? Object.assign({}, s, { week_offset: 0, day: parseInt(meta.index, 10) })
						: Object.assign({}, s);
				});
			},
			segmentMatchesColumnDay: function (seg, dayNum, columnDate, displayWeekOffset, carryoverShift) {
				if (parseInt(seg.day, 10) !== parseInt(dayNum, 10)) {
					return false;
				}
				if (deps.getSegmentDisplayWeekOffset(seg, carryoverShift) !== displayWeekOffset) {
					return false;
				}
				if (columnDate && seg.date) {
					return seg.date === columnDate;
				}
				return true;
			},
			recalculateTasks: function (tasks, dayHours, days, calendarConfig, semanaInicio, options) {
				var mapped = planVisibleWeek(tasks, dayHours, days, calendarConfig, semanaInicio, options || {});
				return (options && options.returnCarryover) ? mapped : mapped.tasks;
			}
		};
	}

	global.LogisticaPlaneacionPlanner = {
		VERSION: PLANNER_VERSION,
		TIMEZONE: PLANEACION_TIMEZONE,
		getTodayYmd: getTodayYmdInTimezone,
		currentMinutesNow: currentMinutesInTimezone,
		weekStartFromDate: weekStartFromDate,
		create: createPlanner
	};

	// Alias compatible con integraciones previas
	global.LogisticaPlaneacionScheduler = global.LogisticaPlaneacionPlanner;
}(typeof window !== 'undefined' ? window : this));
