package schedule

import "fmt"

// validateLocalOrderForSinglePlacement 校验单个任务落到目标时段后，是否仍满足同任务类内部顺序约束。
//
// 职责边界：
// 1. 只负责“同任务类内部顺序”这一条规则，不负责冲突、锁定、范围合法性；
// 2. 采用“克隆态 + 假设落位”方式校验，避免直接污染真实 state；
// 3. 若任务不属于 task_item / 缺少 task_order / 当前无边界约束，直接放行。
func validateLocalOrderForSinglePlacement(state *ScheduleState, taskID int, targetSlots []TaskSlot) error {
	if len(targetSlots) == 0 {
		return nil
	}
	return validateLocalOrderBatchPlacement(state, map[int][]TaskSlot{
		taskID: cloneScheduleTaskSlots(targetSlots),
	})
}

// validateLocalOrderBatchPlacement 在“多任务同时变更”的假设下做顺序约束校验。
//
// 职责边界：
// 1. 先把所有候选落位一次性写入克隆态，再统一校验，避免批量局部调整时出现伪冲突；
// 2. 只校验 proposals 中涉及的任务，因为只要这些任务仍处于各自前驱/后继之间，就不会破坏同类整体顺序；
// 3. 返回首个命中的中文错误，供写工具直接透传给 LLM。
func validateLocalOrderBatchPlacement(state *ScheduleState, proposals map[int][]TaskSlot) error {
	if state == nil || len(proposals) == 0 {
		return nil
	}

	clone := state.Clone()
	for taskID, slots := range proposals {
		task := clone.TaskByStateID(taskID)
		if task == nil {
			return fmt.Errorf("顺序约束校验失败：任务ID %d 不存在", taskID)
		}
		task.Slots = cloneScheduleTaskSlots(slots)
	}

	for taskID := range proposals {
		if err := validateTaskLocalOrderOnState(clone, taskID); err != nil {
			return err
		}
	}
	return nil
}

// validateTaskLocalOrderOnState 判断某个任务在当前假设态下，是否仍处于同任务类前驱/后继之间。
func validateTaskLocalOrderOnState(state *ScheduleState, taskID int) error {
	task := state.TaskByStateID(taskID)
	if task == nil {
		return fmt.Errorf("顺序约束校验失败：任务ID %d 不存在", taskID)
	}
	if !shouldEnforceTaskLocalOrder(*task) || len(task.Slots) == 0 {
		return nil
	}

	prevTask, nextTask := findTaskClassNeighbors(state, *task)
	targetStartDay, targetStartSlot, _ := earliestScheduleTaskSlot(task.Slots)
	targetEndDay, _, targetEndSlot := latestScheduleTaskSlot(task.Slots)

	if prevTask != nil && len(prevTask.Slots) > 0 {
		prevEndDay, _, prevEndSlot := latestScheduleTaskSlot(prevTask.Slots)
		if !isStrictlyAfter(targetStartDay, targetStartSlot, prevEndDay, prevEndSlot) {
			return fmt.Errorf(
				"顺序约束不满足：[%d]%s 不能放到%s。它必须晚于同任务类前一个任务 %s 的结束位置（%s）。",
				task.StateID,
				task.Name,
				formatTaskSlotsBriefWithState(state, task.Slots),
				formatTaskLabel(*prevTask),
				formatTaskSlotsBriefWithState(state, prevTask.Slots),
			)
		}
	}

	if nextTask != nil && len(nextTask.Slots) > 0 {
		nextStartDay, nextStartSlot, _ := earliestScheduleTaskSlot(nextTask.Slots)
		if !isStrictlyBefore(targetEndDay, targetEndSlot, nextStartDay, nextStartSlot) {
			return fmt.Errorf(
				"顺序约束不满足：[%d]%s 不能放到%s。它必须早于同任务类后一个任务 %s 的开始位置（%s）。",
				task.StateID,
				task.Name,
				formatTaskSlotsBriefWithState(state, task.Slots),
				formatTaskLabel(*nextTask),
				formatTaskSlotsBriefWithState(state, nextTask.Slots),
			)
		}
	}

	return nil
}

// shouldEnforceTaskLocalOrder 判断任务是否需要参与“同任务类内部顺序”约束。
func shouldEnforceTaskLocalOrder(task ScheduleTask) bool {
	return task.Source == "task_item" && task.TaskClassID > 0 && task.TaskOrder > 0
}

// findTaskClassNeighbors 查找同任务类中 order 紧邻当前任务的前驱与后继。
func findTaskClassNeighbors(state *ScheduleState, task ScheduleTask) (prevTask *ScheduleTask, nextTask *ScheduleTask) {
	if state == nil || !shouldEnforceTaskLocalOrder(task) {
		return nil, nil
	}

	for i := range state.Tasks {
		candidate := &state.Tasks[i]
		if candidate.StateID == task.StateID {
			continue
		}
		if !shouldEnforceTaskLocalOrder(*candidate) {
			continue
		}
		if candidate.TaskClassID != task.TaskClassID {
			continue
		}

		if candidate.TaskOrder < task.TaskOrder {
			if prevTask == nil || candidate.TaskOrder > prevTask.TaskOrder {
				prevTask = candidate
			}
			continue
		}
		if candidate.TaskOrder > task.TaskOrder {
			if nextTask == nil || candidate.TaskOrder < nextTask.TaskOrder {
				nextTask = candidate
			}
		}
	}
	return prevTask, nextTask
}

func earliestScheduleTaskSlot(slots []TaskSlot) (day int, slotStart int, slotEnd int) {
	if len(slots) == 0 {
		return 0, 0, 0
	}
	best := slots[0]
	for i := 1; i < len(slots); i++ {
		current := slots[i]
		if current.Day < best.Day ||
			(current.Day == best.Day && current.SlotStart < best.SlotStart) ||
			(current.Day == best.Day && current.SlotStart == best.SlotStart && current.SlotEnd < best.SlotEnd) {
			best = current
		}
	}
	return best.Day, best.SlotStart, best.SlotEnd
}

func latestScheduleTaskSlot(slots []TaskSlot) (day int, slotStart int, slotEnd int) {
	if len(slots) == 0 {
		return 0, 0, 0
	}
	best := slots[0]
	for i := 1; i < len(slots); i++ {
		current := slots[i]
		if current.Day > best.Day ||
			(current.Day == best.Day && current.SlotEnd > best.SlotEnd) ||
			(current.Day == best.Day && current.SlotEnd == best.SlotEnd && current.SlotStart > best.SlotStart) {
			best = current
		}
	}
	return best.Day, best.SlotStart, best.SlotEnd
}

func isStrictlyAfter(dayA, slotA, dayB, slotB int) bool {
	if dayA != dayB {
		return dayA > dayB
	}
	return slotA > slotB
}

func isStrictlyBefore(dayA, slotA, dayB, slotB int) bool {
	if dayA != dayB {
		return dayA < dayB
	}
	return slotA < slotB
}

func cloneScheduleTaskSlots(src []TaskSlot) []TaskSlot {
	if len(src) == 0 {
		return nil
	}
	dst := make([]TaskSlot, len(src))
	copy(dst, src)
	return dst
}
