package newagenttools

import (
	"fmt"
	"sort"
	"strings"

	"github.com/LoveLosita/smartflow/backend/newAgent/tools/schedule"
)

type scheduleTaskSnapshot struct {
	Exists  bool
	TaskID  int
	Name    string
	Status  string
	Slots   []schedule.TaskSlot
	DayInfo map[int]schedule.DayMapping
}

type scheduleQueueSnapshot struct {
	PendingCount   int
	CompletedCount int
	SkippedCount   int
	CurrentTaskID  int
	CurrentAttempt int
	LastError      string
}

// NewPlaceToolHandler 返回 place 的结构化结果 handler（第一轮真实 result_view）。
func NewPlaceToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		taskID, ok := schedule.ArgsInt(args, "task_id")
		if !ok {
			return buildScheduleArgErrorResult("place", args, "缺少必填参数 task_id。", state)
		}
		day, ok := schedule.ArgsInt(args, "day")
		if !ok {
			return buildScheduleArgErrorResult("place", args, "缺少必填参数 day。", state)
		}
		slotStart, ok := schedule.ArgsInt(args, "slot_start")
		if !ok {
			return buildScheduleArgErrorResult("place", args, "缺少必填参数 slot_start。", state)
		}
		if state == nil {
			return buildScheduleArgErrorResult("place", args, "日程状态为空，无法执行预排。", nil)
		}

		beforeState := state.Clone()
		observation := schedule.Place(state, taskID, day, slotStart)
		afterState := state.Clone()

		before := snapshotTask(beforeState, taskID)
		after := snapshotTask(afterState, taskID)
		success := after.Exists && taskHasSlotAt(after, day, slotStart) && !sameSlots(before.Slots, after.Slots)

		changes := []map[string]any{
			buildTaskChange("place", before, after),
		}
		affectedDays := collectAffectedDays(changes)
		return buildScheduleOperationResult("place", args, afterState, observation, success, affectedDays, changes, nil, "")
	}
}

// NewMoveToolHandler 返回 move 的结构化结果 handler（第一轮真实 result_view）。
func NewMoveToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		taskID, ok := schedule.ArgsInt(args, "task_id")
		if !ok {
			return buildScheduleArgErrorResult("move", args, "缺少必填参数 task_id。", state)
		}
		newDay, ok := schedule.ArgsInt(args, "new_day")
		if !ok {
			return buildScheduleArgErrorResult("move", args, "缺少必填参数 new_day。", state)
		}
		newSlotStart, ok := schedule.ArgsInt(args, "new_slot_start")
		if !ok {
			return buildScheduleArgErrorResult("move", args, "缺少必填参数 new_slot_start。", state)
		}
		if state == nil {
			return buildScheduleArgErrorResult("move", args, "日程状态为空，无法执行移动。", nil)
		}

		beforeState := state.Clone()
		observation := schedule.Move(state, taskID, newDay, newSlotStart)
		afterState := state.Clone()

		before := snapshotTask(beforeState, taskID)
		after := snapshotTask(afterState, taskID)
		success := before.Exists && after.Exists && taskHasSlotAt(after, newDay, newSlotStart) && !sameSlots(before.Slots, after.Slots)

		changes := []map[string]any{
			buildTaskChange("move", before, after),
		}
		affectedDays := collectAffectedDays(changes)
		return buildScheduleOperationResult("move", args, afterState, observation, success, affectedDays, changes, nil, "")
	}
}

// NewSwapToolHandler 返回 swap 的结构化结果 handler（第一轮真实 result_view）。
func NewSwapToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		taskA, ok := schedule.ArgsInt(args, "task_a")
		if !ok {
			return buildScheduleArgErrorResult("swap", args, "缺少必填参数 task_a。", state)
		}
		taskB, ok := schedule.ArgsInt(args, "task_b")
		if !ok {
			return buildScheduleArgErrorResult("swap", args, "缺少必填参数 task_b。", state)
		}
		if state == nil {
			return buildScheduleArgErrorResult("swap", args, "日程状态为空，无法执行交换。", nil)
		}

		beforeState := state.Clone()
		observation := schedule.Swap(state, taskA, taskB)
		afterState := state.Clone()

		beforeA := snapshotTask(beforeState, taskA)
		afterA := snapshotTask(afterState, taskA)
		beforeB := snapshotTask(beforeState, taskB)
		afterB := snapshotTask(afterState, taskB)

		success := beforeA.Exists &&
			beforeB.Exists &&
			afterA.Exists &&
			afterB.Exists &&
			sameSlots(beforeA.Slots, afterB.Slots) &&
			sameSlots(beforeB.Slots, afterA.Slots)

		changes := []map[string]any{
			buildTaskChange("swap", beforeA, afterA),
			buildTaskChange("swap", beforeB, afterB),
		}
		affectedDays := collectAffectedDays(changes)
		return buildScheduleOperationResult("swap", args, afterState, observation, success, affectedDays, changes, nil, "")
	}
}

// NewBatchMoveToolHandler 返回 batch_move 的结构化结果 handler（第一轮真实 result_view）。
func NewBatchMoveToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		if state == nil {
			return buildScheduleArgErrorResult("batch_move", args, "日程状态为空，无法执行批量移动。", nil)
		}
		moves, err := schedule.ArgsMoveList(args)
		if err != nil {
			return buildScheduleArgErrorResult("batch_move", args, err.Error(), state)
		}

		beforeState := state.Clone()
		observation := schedule.BatchMove(state, moves)
		afterState := state.Clone()

		changes := make([]map[string]any, 0, len(moves))
		success := len(moves) > 0
		for _, move := range moves {
			before := snapshotTask(beforeState, move.TaskID)
			after := snapshotTask(afterState, move.TaskID)
			changes = append(changes, buildTaskChange("batch_move", before, after))
			if !after.Exists || !taskHasSlotAt(after, move.NewDay, move.NewSlotStart) {
				success = false
			}
		}

		affectedDays := collectAffectedDays(changes)
		return buildScheduleOperationResult("batch_move", args, afterState, observation, success, affectedDays, changes, nil, "")
	}
}

// NewUnplaceToolHandler 返回 unplace 的结构化结果 handler（第一轮真实 result_view）。
func NewUnplaceToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		taskID, ok := schedule.ArgsInt(args, "task_id")
		if !ok {
			return buildScheduleArgErrorResult("unplace", args, "缺少必填参数 task_id。", state)
		}
		if state == nil {
			return buildScheduleArgErrorResult("unplace", args, "日程状态为空，无法执行移出。", nil)
		}

		beforeState := state.Clone()
		observation := schedule.Unplace(state, taskID)
		afterState := state.Clone()

		before := snapshotTask(beforeState, taskID)
		after := snapshotTask(afterState, taskID)
		success := before.Exists && len(before.Slots) > 0 && len(after.Slots) == 0 && strings.EqualFold(strings.TrimSpace(after.Status), schedule.TaskStatusPending)

		changes := []map[string]any{
			buildTaskChange("unplace", before, after),
		}
		affectedDays := collectAffectedDays(changes)
		return buildScheduleOperationResult("unplace", args, afterState, observation, success, affectedDays, changes, nil, "")
	}
}

// NewQueueApplyHeadMoveToolHandler 返回 queue_apply_head_move 的结构化结果 handler（第一轮真实 result_view）。
func NewQueueApplyHeadMoveToolHandler() ToolHandler {
	return func(state *schedule.ScheduleState, args map[string]any) ToolExecutionResult {
		newDay, dayOK := schedule.ArgsInt(args, "new_day")
		newSlotStart, slotOK := schedule.ArgsInt(args, "new_slot_start")
		if state == nil {
			return buildScheduleArgErrorResult("queue_apply_head_move", args, "日程状态为空，无法执行队首任务应用。", nil)
		}

		// 1. 执行前先记录 current 任务与队列快照，保证成功/失败都可构造稳定结构化视图。
		// 2. 再执行工具并抓取执行后快照，基于 before/after 计算差异，不依赖自然语言解析。
		// 3. 如果快照构造异常，外层仍会回退 LegacyResult，保证工具主链路不被展示层影响。
		beforeState := state.Clone()
		beforeQueue := snapshotQueue(beforeState)
		currentTaskID := 0
		if beforeState != nil && beforeState.RuntimeQueue != nil {
			currentTaskID = beforeState.RuntimeQueue.CurrentTaskID
		}
		beforeTask := snapshotTask(beforeState, currentTaskID)

		observation := schedule.QueueApplyHeadMove(state, args)

		afterState := state.Clone()
		afterQueue := snapshotQueue(afterState)
		afterTask := snapshotTask(afterState, currentTaskID)

		success := false
		if payload, ok := parseObservationJSON(strings.TrimSpace(observation)); ok {
			if parsedSuccess, exists := payload["success"].(bool); exists {
				success = parsedSuccess
			}
		}
		if !success {
			success = currentTaskID > 0 &&
				(afterQueue.CompletedCount > beforeQueue.CompletedCount) &&
				(afterQueue.CurrentTaskID != currentTaskID)
			if dayOK && slotOK && success {
				success = taskHasSlotAt(afterTask, newDay, newSlotStart)
			}
		}

		changes := []map[string]any{
			buildTaskChange("queue_apply_head_move", beforeTask, afterTask),
		}
		affectedDays := collectAffectedDays(changes)
		queueSnapshot := buildQueueSnapshotWithLabels(beforeQueue, afterQueue)
		return buildScheduleOperationResult(
			"queue_apply_head_move",
			args,
			afterState,
			observation,
			success,
			affectedDays,
			changes,
			queueSnapshot,
			pickFailureReason(observation, success),
		)
	}
}

func buildScheduleArgErrorResult(toolName string, args map[string]any, reason string, state *schedule.ScheduleState) ToolExecutionResult {
	observation := fmt.Sprintf("%s失败：%s", scheduleOperationFailurePrefix(toolName), strings.TrimSpace(reason))
	return buildScheduleOperationResult(
		toolName,
		args,
		state,
		observation,
		false,
		nil,
		nil,
		nil,
		strings.TrimSpace(reason),
	)
}

func buildScheduleOperationResult(
	toolName string,
	args map[string]any,
	displayState *schedule.ScheduleState,
	observation string,
	success bool,
	affectedDays []int,
	changes []map[string]any,
	queueSnapshot map[string]any,
	failureReason string,
) ToolExecutionResult {
	result := LegacyResultWithState(toolName, args, displayState, observation)

	status := ToolStatusFailed
	if success {
		status = ToolStatusDone
	}
	operationLabel := resolveOperationLabelCN(toolName)
	title := fmt.Sprintf("%s%s", operationLabel, resolveResultTitleSuffix(status))
	subtitle := buildScheduleSubtitle(toolName, changes, success)
	metrics := []map[string]any{
		{"label": "任务数量", "value": fmt.Sprintf("%d个", maxInt(len(changes), countMovesFromArgs(args)))},
		{"label": "影响天数", "value": fmt.Sprintf("%d天", len(affectedDays))},
	}

	collapsed := map[string]any{
		"title":               title,
		"subtitle":            subtitle,
		"status":              status,
		"status_label":        resolveToolStatusLabelCN(status),
		"operation":           strings.TrimSpace(toolName),
		"operation_label":     operationLabel,
		"task_count":          maxInt(len(changes), countMovesFromArgs(args)),
		"affected_days_count": len(affectedDays),
		"metrics":             metrics,
	}

	expanded := map[string]any{
		"operation":           strings.TrimSpace(toolName),
		"operation_label":     operationLabel,
		"changes":             changes,
		"affected_days":       affectedDays,
		"affected_days_label": formatAffectedDaysLabel(affectedDays),
		"raw_text":            observation,
	}
	if queueSnapshot != nil {
		expanded["queue_snapshot"] = queueSnapshot
	}
	if !success {
		expanded["failure_reason"] = strings.TrimSpace(pickFailureReason(failureReason, false))
	}

	result.Status = status
	result.Success = success
	result.Summary = title
	result.ArgumentsPreview = readArgumentSummary(result.ArgumentView)
	result.ResultView = &ToolDisplayView{
		ViewType:  "schedule.operation_result",
		Version:   1,
		Collapsed: collapsed,
		Expanded:  expanded,
	}
	if !success {
		result.ErrorCode = "schedule_operation_failed"
		if strings.TrimSpace(result.ErrorMessage) == "" {
			result.ErrorMessage = strings.TrimSpace(pickFailureReason(failureReason, false))
		}
	}
	return EnsureToolResultDefaults(result, args)
}

func buildScheduleSubtitle(operation string, changes []map[string]any, success bool) string {
	if len(changes) == 0 {
		if success {
			return fmt.Sprintf("%s已完成", resolveOperationLabelCN(operation))
		}
		return fmt.Sprintf("%s执行失败", resolveOperationLabelCN(operation))
	}

	firstTask := readStringMap(changes[0], "task_label")
	firstBefore := readStringMap(changes[0], "before_label")
	firstAfter := readStringMap(changes[0], "after_label")

	switch strings.TrimSpace(operation) {
	case "move":
		return fmt.Sprintf("%s：从%s移动到%s", firstTask, firstBefore, firstAfter)
	case "place":
		return fmt.Sprintf("%s：预排到%s", firstTask, firstAfter)
	case "unplace":
		return fmt.Sprintf("%s：已从%s移出", firstTask, firstBefore)
	case "swap":
		if len(changes) >= 2 {
			secondTask := readStringMap(changes[1], "task_label")
			return fmt.Sprintf("%s 与 %s 已交换位置", firstTask, secondTask)
		}
		return fmt.Sprintf("%s已交换位置", firstTask)
	case "batch_move":
		return fmt.Sprintf("批量移动 %d 个任务", len(changes))
	case "queue_apply_head_move":
		if success {
			return fmt.Sprintf("队首任务已移动到%s", firstAfter)
		}
		return fmt.Sprintf("队首任务移动失败：%s", firstTask)
	default:
		return fmt.Sprintf("%s：%s", resolveOperationLabelCN(operation), firstTask)
	}
}

func resolveResultTitleSuffix(status string) string {
	switch normalizeToolStatus(status) {
	case ToolStatusDone:
		return "成功"
	case ToolStatusBlocked:
		return "已阻断"
	default:
		return "失败"
	}
}

func pickFailureReason(raw string, success bool) string {
	if success {
		return ""
	}
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return "操作失败，请查看原始结果。"
	}
	if payload, ok := parseObservationJSON(trimmed); ok {
		if text, ok := readStringFromMap(payload, "result", "error", "reason", "message", "err"); ok {
			return strings.TrimSpace(text)
		}
	}
	return trimmed
}

func snapshotTask(state *schedule.ScheduleState, taskID int) scheduleTaskSnapshot {
	if state == nil || taskID <= 0 {
		return scheduleTaskSnapshot{
			Exists:  false,
			TaskID:  taskID,
			DayInfo: buildDayInfo(state),
		}
	}
	task := state.TaskByStateID(taskID)
	if task == nil {
		return scheduleTaskSnapshot{
			Exists:  false,
			TaskID:  taskID,
			DayInfo: buildDayInfo(state),
		}
	}
	return scheduleTaskSnapshot{
		Exists:  true,
		TaskID:  task.StateID,
		Name:    strings.TrimSpace(task.Name),
		Status:  strings.TrimSpace(task.Status),
		Slots:   cloneSlots(task.Slots),
		DayInfo: buildDayInfo(state),
	}
}

func snapshotQueue(state *schedule.ScheduleState) scheduleQueueSnapshot {
	if state == nil || state.RuntimeQueue == nil {
		return scheduleQueueSnapshot{}
	}
	return scheduleQueueSnapshot{
		PendingCount:   len(state.RuntimeQueue.PendingTaskIDs),
		CompletedCount: len(state.RuntimeQueue.CompletedTaskIDs),
		SkippedCount:   len(state.RuntimeQueue.SkippedTaskIDs),
		CurrentTaskID:  state.RuntimeQueue.CurrentTaskID,
		CurrentAttempt: state.RuntimeQueue.CurrentAttempts,
		LastError:      strings.TrimSpace(state.RuntimeQueue.LastError),
	}
}

func buildQueueSnapshotWithLabels(before scheduleQueueSnapshot, after scheduleQueueSnapshot) map[string]any {
	return map[string]any{
		"before":           queueSnapshotToMap(before),
		"after":            queueSnapshotToMap(after),
		"before_label":     queueSummaryLabel(before),
		"after_label":      queueSummaryLabel(after),
		"summary_label":    queueSummaryLabel(after),
		"last_error_label": strings.TrimSpace(after.LastError),
	}
}

func queueSnapshotToMap(s scheduleQueueSnapshot) map[string]any {
	return map[string]any{
		"pending_count":   s.PendingCount,
		"completed_count": s.CompletedCount,
		"skipped_count":   s.SkippedCount,
		"current_task_id": s.CurrentTaskID,
		"current_attempt": s.CurrentAttempt,
		"last_error":      s.LastError,
	}
}

func queueSummaryLabel(s scheduleQueueSnapshot) string {
	return fmt.Sprintf(
		"待处理%d个，已完成%d个，已跳过%d个，当前任务%d，尝试%d次",
		s.PendingCount,
		s.CompletedCount,
		s.SkippedCount,
		s.CurrentTaskID,
		s.CurrentAttempt,
	)
}

func buildTaskChange(operation string, before scheduleTaskSnapshot, after scheduleTaskSnapshot) map[string]any {
	taskLabel := resolveChangeTaskLabel(before, after)
	beforeStatusLabel := resolveTaskStatusLabelCN(before.Status)
	afterStatusLabel := resolveTaskStatusLabelCN(after.Status)
	beforeLabel := formatPlacementLabel(operation, before.Slots, before.Status, false, false)
	afterLabel := formatPlacementLabel(operation, after.Slots, after.Status, true, len(before.Slots) > 0)

	change := map[string]any{
		"task_id": before.TaskID,
		"name":    firstNonEmpty(before.Name, after.Name),
		"status": map[string]any{
			"before": before.Status,
			"after":  after.Status,
		},
		"before_slots":  slotsToView(before.Slots, before.DayInfo),
		"after_slots":   slotsToView(after.Slots, after.DayInfo),
		"task_label":    taskLabel,
		"before_label":  beforeLabel,
		"after_label":   afterLabel,
		"status_label":  fmt.Sprintf("%s -> %s", beforeStatusLabel, afterStatusLabel),
		"operation_key": operation,
	}
	return change
}

func resolveChangeTaskLabel(before scheduleTaskSnapshot, after scheduleTaskSnapshot) string {
	name := firstNonEmpty(before.Name, after.Name)
	if name == "" {
		if before.TaskID > 0 {
			return fmt.Sprintf("[%d]任务", before.TaskID)
		}
		return "任务"
	}
	if before.TaskID > 0 {
		return fmt.Sprintf("[%d]%s", before.TaskID, name)
	}
	return name
}

func resolveTaskStatusLabelCN(status string) string {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case schedule.TaskStatusPending:
		return "待安排"
	case schedule.TaskStatusSuggested:
		return "已预排"
	case schedule.TaskStatusExisting:
		return "已安排"
	default:
		return "未知状态"
	}
}

func formatPlacementLabel(operation string, slots []schedule.TaskSlot, status string, isAfter bool, hadBefore bool) string {
	if len(slots) > 0 {
		return formatSlotsLabelCN(slots)
	}
	if isAfter && strings.TrimSpace(operation) == "unplace" && hadBefore {
		return "已移出"
	}
	if strings.EqualFold(strings.TrimSpace(status), schedule.TaskStatusPending) {
		return "未安排"
	}
	return "未安排"
}

func formatSlotsLabelCN(slots []schedule.TaskSlot) string {
	if len(slots) == 0 {
		return "未安排"
	}
	parts := make([]string, 0, len(slots))
	for _, slot := range slots {
		parts = append(parts, fmt.Sprintf("%s %s", formatDayLabelCN(slot.Day), formatSlotRangeCN(slot.SlotStart, slot.SlotEnd)))
	}
	return strings.Join(parts, "、")
}

func formatAffectedDaysLabel(affectedDays []int) string {
	if len(affectedDays) == 0 {
		return "无"
	}
	parts := make([]string, 0, len(affectedDays))
	for _, day := range affectedDays {
		parts = append(parts, formatDayLabelCN(day))
	}
	return strings.Join(parts, "、")
}

func slotsToView(slots []schedule.TaskSlot, dayInfo map[int]schedule.DayMapping) []map[string]any {
	if len(slots) == 0 {
		return make([]map[string]any, 0)
	}
	result := make([]map[string]any, 0, len(slots))
	for _, slot := range slots {
		entry := map[string]any{
			"day":        slot.Day,
			"slot_start": slot.SlotStart,
			"slot_end":   slot.SlotEnd,
		}
		if info, ok := dayInfo[slot.Day]; ok {
			entry["week"] = info.Week
			entry["day_of_week"] = info.DayOfWeek
		}
		result = append(result, entry)
	}
	return result
}

func collectAffectedDays(changes []map[string]any) []int {
	if len(changes) == 0 {
		return make([]int, 0)
	}
	set := make(map[int]struct{})
	for _, change := range changes {
		collectDaysFromSlotView(set, change["before_slots"])
		collectDaysFromSlotView(set, change["after_slots"])
	}
	days := make([]int, 0, len(set))
	for day := range set {
		days = append(days, day)
	}
	sort.Ints(days)
	return days
}

func collectDaysFromSlotView(target map[int]struct{}, raw any) {
	list, ok := raw.([]map[string]any)
	if ok {
		for _, item := range list {
			day, ok := item["day"].(int)
			if ok {
				target[day] = struct{}{}
			}
		}
		return
	}

	anyList, ok := raw.([]any)
	if !ok {
		return
	}
	for _, item := range anyList {
		itemMap, ok := item.(map[string]any)
		if !ok {
			continue
		}
		switch day := itemMap["day"].(type) {
		case int:
			target[day] = struct{}{}
		case float64:
			target[int(day)] = struct{}{}
		}
	}
}

func buildDayInfo(state *schedule.ScheduleState) map[int]schedule.DayMapping {
	if state == nil || len(state.Window.DayMapping) == 0 {
		return map[int]schedule.DayMapping{}
	}
	info := make(map[int]schedule.DayMapping, len(state.Window.DayMapping))
	for _, item := range state.Window.DayMapping {
		info[item.DayIndex] = item
	}
	return info
}

func cloneSlots(slots []schedule.TaskSlot) []schedule.TaskSlot {
	if len(slots) == 0 {
		return nil
	}
	out := make([]schedule.TaskSlot, len(slots))
	copy(out, slots)
	return out
}

func sameSlots(a []schedule.TaskSlot, b []schedule.TaskSlot) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i].Day != b[i].Day || a[i].SlotStart != b[i].SlotStart || a[i].SlotEnd != b[i].SlotEnd {
			return false
		}
	}
	return true
}

func taskHasSlotAt(snapshot scheduleTaskSnapshot, day int, slotStart int) bool {
	for _, slot := range snapshot.Slots {
		if slot.Day == day && slot.SlotStart == slotStart {
			return true
		}
	}
	return false
}

func readStringMap(input map[string]any, key string) string {
	value, ok := input[key]
	if !ok || value == nil {
		return ""
	}
	text, _ := value.(string)
	return strings.TrimSpace(text)
}

func countMovesFromArgs(args map[string]any) int {
	moves, ok := args["moves"].([]any)
	if !ok {
		return 0
	}
	return len(moves)
}

func maxInt(values ...int) int {
	if len(values) == 0 {
		return 0
	}
	maxValue := values[0]
	for _, value := range values[1:] {
		if value > maxValue {
			maxValue = value
		}
	}
	return maxValue
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func scheduleOperationFailurePrefix(toolName string) string {
	switch strings.TrimSpace(toolName) {
	case "place":
		return "放置"
	case "move", "queue_apply_head_move":
		return "移动"
	case "swap":
		return "交换"
	case "batch_move":
		return "批量移动"
	case "unplace":
		return "移除"
	default:
		return strings.TrimSpace(toolName)
	}
}
