package logic

import (
	"github.com/LoveLosita/smartflow/backend/conv"
	"github.com/LoveLosita/smartflow/backend/model"
	"github.com/LoveLosita/smartflow/backend/respond"
)

type slotStatus int

const (
	Free     slotStatus = iota // 0: 纯空闲
	Occupied                   // 1: 已有课/任务，不可动
	Blocked                    // 2: 用户屏蔽时段
	Filler                     // 3: 水课，允许嵌入
)

type slotNode struct {
	Status  slotStatus
	EventID uint // 🚀 关键：记录课程 ID，用于识别水课边界
}

type grid struct {
	data      map[int]map[int][13]slotNode
	startWeek int
	startDay  int
	endWeek   int
	endDay    int
}

// getNode 和 setNode 是对 grid 数据结构的封装，确保我们在访问时能正确处理默认值（Free）和边界情况
func (g *grid) getNode(w, d, s int) slotNode {
	if dayMap, ok := g.data[w]; ok {
		return dayMap[d][s]
	}
	return slotNode{Status: Free, EventID: 0}
}

func (g *grid) setNode(w, d, s int, node slotNode) {
	if _, ok := g.data[w]; !ok {
		g.data[w] = make(map[int][13]slotNode)
	}
	dayData := g.data[w][d]
	dayData[s] = node
	g.data[w][d] = dayData
}

// 检查是否可用 (Free 或 Filler 且不在 Blocked 时段内)
func (g *grid) isAvailable(w, d, s int) bool {
	node := g.getNode(w, d, s)
	return node.Status == Free || node.Status == Filler
}

// countAvailableSlots 统计指定周次范围内所有可用的原子节次总数
func (g *grid) countAvailableSlots(currW, currD, currS int) int {
	count := 0
	if currW == 0 && currD == 0 && currS == 0 {
		currW, currD, currS = g.startWeek, g.startDay, 1
	}
	for w := currW; w <= g.endWeek; w++ {
		dayMap, hasData := g.data[w]
		for d := 1; d <= 7; d++ {
			// 🚀 头部裁剪：过滤开始日期前的天数
			if w == currW && d < currD {
				continue
			}
			// 🚀 尾部裁剪：过滤结束日期后的天数
			if w == g.endWeek && d > g.endDay {
				break
			}
			var dayData [13]slotNode
			if hasData {
				dayData = dayMap[d]
			}
			for s := 1; s <= 12; s++ {
				if w == currW && d == currD && s < currS {
					continue
				}
				if dayData[s].Status == Free || dayData[s].Status == Filler {
					count++
				}
			}
		}
	}
	return count
}

// FindNextAvailable 从当前时间点开始，按周、天、节次顺序查找下一个可用格子
func (g *grid) FindNextAvailable(currW, currD, currS int) (int, int, int) {
	// 基础越界检查
	if currW > g.endWeek || (currW == g.endWeek && currD > g.endDay) {
		return -1, -1, -1
	}

	for w := currW; w <= g.endWeek; w++ {
		dayMap, hasData := g.data[w]
		for d := 1; d <= 7; d++ {
			if w == currW && d < currD {
				continue
			}
			if w == g.endWeek && d > g.endDay {
				break
			} // 🚀 守住结束天

			var dayData [13]slotNode
			if hasData {
				dayData = dayMap[d]
			}

			for s := 1; s <= 12; s++ {
				if w == currW && d == currD && s < currS {
					continue
				}
				if w == g.endWeek && d == g.endDay {
					break
				} // 🚀 守住结束节

				if dayData[s].Status == Free || dayData[s].Status == Filler {
					return w, d, s
				}
			}
		}
	}
	return -1, -1, -1
}

// 辅助函数：向后跳过指定数量的可用坑位
func (g *grid) skipAvailableSlots(w, d, s, skipCount int) (int, int, int) {
	if skipCount <= 0 {
		// 即使 gap 为 0，也要至少移到下一节
		s++
		if s > 12 {
			s = 1
			d++
			if d > 7 {
				d = 1
				w++
			}
		}
		return w, d, s
	}
	found := 0
	currW, currD, currS := w, d, s+1
	for currW <= g.endWeek {
		if currS > 12 {
			currS = 1
			currD++
			if currD > 7 {
				currD = 1
				currW++
			}
			continue
		}
		// 如果已经跳到了最后一天，不要再跳了，直接返回终点坐标
		if currW == g.endWeek && currD > g.endDay {
			return g.endWeek, g.endDay, 12
		}
		if g.isAvailable(currW, currD, currS) {
			found++
			if found > skipCount {
				return currW, currD, currS
			}
		}
		currS++
	}
	return currW, currD, currS
}

func SmartPlanningMainLogic(schedules []model.Schedule, taskClass *model.TaskClass) ([]model.UserWeekSchedule, error) {
	//1.先构建时间格子
	g := buildTimeGrid(schedules, taskClass)
	//2.根据时间格子和排课策略计算每个任务块的具体安排时间
	allocatedItems, err := computeAllocation(g, taskClass.Items, *taskClass.Strategy)
	if err != nil {
		return nil, err
	}
	//3.把这些时间通过DTO函数回填到涉及周的 UserWeekSchedule 结构中，供前端展示
	return conv.PlanningResultToUserWeekSchedules(schedules, allocatedItems), nil
}

// buildTimeGrid 构建一个时间格子，标记出哪些时间段被占用、哪些被屏蔽、哪些是水课

func buildTimeGrid(schedules []model.Schedule, taskClass *model.TaskClass) *grid {
	// 🚀 核心修正：获取精确的起始坐标
	startW, startD, _ := conv.RealDateToRelativeDate(taskClass.StartDate.Format(conv.DateFormat))
	endW, endD, _ := conv.RealDateToRelativeDate(taskClass.EndDate.Format(conv.DateFormat))
	// 将信息初始化到 grid 结构中
	g := &grid{
		data:      make(map[int]map[int][13]slotNode),
		startWeek: startW,
		startDay:  startD,
		endWeek:   endW,
		endDay:    endD,
	}
	//标记屏蔽时段 (Blocked)
	for _, blockIdx := range taskClass.ExcludedSlots {
		sFrom, sTo := (blockIdx-1)*2+1, blockIdx*2
		for w := startW; w <= endW; w++ {
			for d := 1; d <= 7; d++ { //🚀 注意：这里的屏蔽是针对每天的，所以直接循环 1-7 天
				for s := sFrom; s <= sTo; s++ {
					g.setNode(w, d, s, slotNode{Status: Blocked})
				}
			}
		}
	}

	// 映射日程 (尊重 Blocked 且只处理范围内的数据)
	for _, s := range schedules {
		if s.Week >= startW && s.Week <= endW {
			if g.getNode(s.Week, s.DayOfWeek, s.Section).Status == Blocked {
				continue
			}
			status := Occupied
			// 只有当课程允许嵌入且当前事件支持嵌入时，才标记为 Filler
			if *taskClass.AllowFillerCourse && s.Event.CanBeEmbedded {
				status = Filler
			}
			g.setNode(s.Week, s.DayOfWeek, s.Section, slotNode{Status: status, EventID: uint(s.EventID)})
		}
	}
	return g
}

// computeAllocation 是核心函数，负责根据当前的时间格子状态和排课策略，计算出每个任务块的具体安排时间
/*func computeAllocation(g *grid, items []model.TaskClassItem, strategy string) ([]model.TaskClassItem, error) {
	if len(items) == 0 {
		return items, nil
	}

	// 🚀 核心修正 1：获取真正的开始坐标（周、天、节）
	// 这里假设你已经通过 conv 把 StartDate 换成了 w1, d1, s1
	startW := g.startWeek
	startD := g.startDay // 建议从 conv 传入具体的 DayOfWeek
	startS := 1

	// 1. 获取可用资源总量
	totalAvailable := g.countAvailableSlots(0, 0, 0)
	// 假设每个任务块至少占用 2 个原子槽位
	totalRequired := len(items) * 2

	// 🚀 核心改进：容量预判
	if totalAvailable < totalRequired {
		// 如果连最基本的坑位都不够，直接报错，不进行任何编排
		return nil, respond.TimeNotEnoughForAutoScheduling
	}

	// 🚀 核心修正 2：步长改为“逻辑间隔”，不再是物理跳跃
	// gap 表示：每两个任务之间，我们要故意空出多少个“可用位”
	gap := 0
	if strategy == "steady" && totalAvailable > totalRequired {
		gap = (totalAvailable - totalRequired) / (len(items) + 1)
	}

	currW, currD, currS := startW, startD, startS
	lastPlacedIndex := -1
	for i := range items {
		w, d, s := g.FindNextAvailable(currW, currD, currS)
		if w == -1 || w > g.endWeek {
			break
		}

		node := g.getNode(w, d, s)
		slotLen := 2
		if node.Status == Filler {
			slotLen = 1
			currID := node.EventID
			for checkS := s + 1; checkS <= 12; checkS++ {
				if next := g.getNode(w, d, checkS); next.Status == Filler && next.EventID == currID {
					slotLen++
				} else {
					break
				}
			}
		}

		endS := s + slotLen - 1
		items[i].EmbeddedTime = &model.TargetTime{
			SectionFrom: s, SectionTo: endS,
			Week: w, DayOfWeek: d,
		}

		for sec := s; sec <= endS; sec++ {
			g.setNode(w, d, sec, slotNode{Status: Occupied})
		}

		// 🚀 核心修正 3：基于“可用位”推进指针，而非物理索引
		// 我们要在 grid 中向后数出 gap 个可用位置，作为下一个任务的起点
		currW, currD, currS = g.skipAvailableSlots(w, d, endS, gap)

		lastPlacedIndex = i // 记录最后一个成功安放的任务索引
	}
	// 🚀 核心改进：结果完整性校验
	if lastPlacedIndex < len(items)-1 {
				return nil, fmt.Errorf("排程中断：由于时间片碎片化，仅成功安排了 %d/%d 个任务块，请尝试扩充时间范围或删减屏蔽位", lastPlacedIndex+1, len(items))
		return nil, respond.TimeNotEnoughForAutoScheduling
	}

	return items, nil
}*/

type slotCoord struct {
	w, d, s int
}

// getAllAvailable 获取窗口内所有可用的原子节次坐标（逻辑一维化）
func (g *grid) getAllAvailable() []slotCoord {
	var coords []slotCoord
	for w := g.startWeek; w <= g.endWeek; w++ {
		dayMap, hasData := g.data[w]
		for d := 1; d <= 7; d++ {
			// 边界裁剪逻辑
			if w == g.startWeek && d < g.startDay {
				continue
			}
			if w == g.endWeek && d > g.endDay {
				break
			}

			var dayData [13]slotNode
			if hasData {
				dayData = dayMap[d]
			}

			for s := 1; s <= 12; s++ {
				// 顺着你的逻辑，不限开始节次，但需注意状态判定
				if dayData[s].Status == Free || dayData[s].Status == Filler {
					coords = append(coords, slotCoord{w, d, s})
				}
			}
		}
	}
	return coords
}

func computeAllocation(g *grid, items []model.TaskClassItem, strategy string) ([]model.TaskClassItem, error) {
	if len(items) == 0 {
		return items, nil
	}

	// 1. 预处理：提取所有可用坑位
	coords := g.getAllAvailable()
	totalAvailable := len(coords)
	totalRequired := len(items) * 2 // 基础需求：每个任务 2 节

	if totalAvailable < totalRequired {
		return nil, respond.TimeNotEnoughForAutoScheduling
	}

	// 2. 计算精准步长
	gap := 0
	if strategy == "steady" {
		gap = (totalAvailable - totalRequired) / (len(items) + 1)
	}

	// 3. 线性映射分配
	// cursor 是我们在逻辑切片中的“指针”
	cursor := gap
	lastPlacedIndex := -1

	for i := range items {
		if cursor >= totalAvailable {
			break
		}

		// 获取当前逻辑位置对应的物理坐标
		startLoc := coords[cursor]
		w, d, s := startLoc.w, startLoc.d, startLoc.s

		// 4. 容器长度探测 (顺着你的逻辑)
		node := g.getNode(w, d, s)
		slotLen := 2
		if node.Status == Filler {
			slotLen = 1
			currID := node.EventID
			for checkS := s + 1; checkS <= 12; checkS++ {
				if next := g.getNode(w, d, checkS); next.Status == Filler && next.EventID == currID {
					slotLen++
				} else {
					break
				}
			}
		} else if s == 12 || !g.isAvailable(w, d, s+1) {
			// 如果是 Free 区域，但下一节不可用，则被迫设为 1 节
			slotLen = 1
		}

		// 回填时间
		endS := s + slotLen - 1
		items[i].EmbeddedTime = &model.TargetTime{
			SectionFrom: s, SectionTo: endS,
			Week: w, DayOfWeek: d,
		}

		// 标记占用 (物理网格)
		for sec := s; sec <= endS; sec++ {
			g.setNode(w, d, sec, slotNode{Status: Occupied})
		}

		// 🚀 核心进步：逻辑跳跃
		// 既然任务占用了 slotLen 节，我们在逻辑切片中也向后推 slotLen 个位置，再加 gap
		cursor += slotLen + gap
		lastPlacedIndex = i
	}

	if lastPlacedIndex < len(items)-1 {
		return nil, respond.TimeNotEnoughForAutoScheduling
	}

	return items, nil
}
