package main

import (
	"errors"
	"fmt"
	"log/slog"
	"strings"

	"reasonix/internal/bot"
	"reasonix/internal/config"
	"reasonix/internal/control"
	"reasonix/internal/event"
)

// 本文件是 botBridgeHub 对 App 的全部胶水：会话枚举（含后台 detached）、
// 按 tab 寻址的审批/问答/驱动、transcript 公告、订阅持久化。

func (a *App) newBotBridge() *botBridgeHub {
	return newBotBridgeHub(botBridgeDeps{
		sessions:        a.bridgeSessions,
		approveTab:      a.bridgeApprove,
		answerTab:       a.bridgeAnswer,
		notify:          a.botRuntime.SendToAdapter,
		drive:           a.bridgeDrive,
		announce:        a.bridgeAnnounce,
		persistWatchers: a.bridgePersistWatchers,
		takeoverChanged: a.emitProjectTreeChanged,
		logger:          slog.Default(),
	})
}

// bridgeSessions 枚举所有 live 会话：可见 tab 用完整 TabMeta，后台 detached
// 会话补一份轻量快照（controller 仍存活，审批/问答仍可路由）。
func (a *App) bridgeSessions() []bot.DesktopSessionInfo {
	tabs := a.ListTabs()
	out := make([]bot.DesktopSessionInfo, 0, len(tabs)+4)
	seen := make(map[string]bool, len(tabs))
	for _, t := range tabs {
		seen[t.ID] = true
		out = append(out, bot.DesktopSessionInfo{
			TabID:         t.ID,
			Label:         t.Label,
			Workspace:     t.WorkspaceName,
			Topic:         t.TopicTitle,
			Ready:         t.Ready,
			Running:       t.Running,
			PendingPrompt: t.PendingPrompt,
		})
	}
	a.mu.RLock()
	for _, tab := range a.detachedSessions {
		if tab == nil || seen[tab.ID] {
			continue
		}
		seen[tab.ID] = true
		out = append(out, bot.DesktopSessionInfo{
			TabID:    tab.ID,
			Label:    tab.TopicTitle,
			Topic:    tab.TopicTitle,
			Ready:    tab.Ctrl != nil,
			Running:  strings.TrimSpace(tab.ActivityStatus) != "",
			Detached: true,
		})
	}
	a.mu.RUnlock()
	return out
}

// bridgeCtrlByTabID 解析可见与后台 detached 两张表（区别于 ctrlByTabID：
// 那是前端语义，空 tabID 落到活跃 tab，且不看 detached）。
func (a *App) bridgeCtrlByTabID(tabID string) control.SessionAPI {
	a.mu.RLock()
	defer a.mu.RUnlock()
	if tab := a.tabByEventSinkIDLocked(tabID); tab != nil {
		return tab.Ctrl
	}
	return nil
}

func (a *App) bridgeApprove(tabID, id string, allow, session, persist bool) {
	if ctrl := a.bridgeCtrlByTabID(tabID); ctrl != nil {
		ctrl.Approve(id, allow, session, persist)
	}
}

func (a *App) bridgeAnswer(tabID, id string, answers []QuestionAnswer) {
	ctrl := a.bridgeCtrlByTabID(tabID)
	if ctrl == nil {
		return
	}
	out := make([]event.AskAnswer, len(answers))
	for i, an := range answers {
		out[i] = event.AskAnswer{QuestionID: an.QuestionID, Selected: an.Selected}
	}
	ctrl.AnswerQuestion(id, out)
}

// bridgeAnnounce 往会话 transcript 发一条 Notice，桌面用户在聊天流里可见。
func (a *App) bridgeAnnounce(tabID, text string) {
	a.mu.RLock()
	tab := a.tabByEventSinkIDLocked(tabID)
	var sink *tabEventSink
	if tab != nil {
		sink = tab.sink
	}
	a.mu.RUnlock()
	if sink == nil {
		return
	}
	sink.Emit(event.Event{Kind: event.Notice, Level: event.LevelWarn, Text: text})
}

// bridgeDrive 把远程文本提交为可见 tab 的新 turn，并为这一轮挂上事件转发器,
// 让输出流回接管聊天（转发器在 TurnDone 自动卸载）。
func (a *App) bridgeDrive(tabID, text string, route bot.DesktopWatchRoute) error {
	admission, ctrl, err := a.beginTabTurn(tabID, false)
	if err != nil {
		if errors.Is(err, control.ErrTurnRunning) {
			return errDriveBusy
		}
		return err
	}
	defer admission.abort()
	tab := admission.tab
	if tab.sink == nil {
		return fmt.Errorf("会话事件通道不可用，无法驱动")
	}
	// A local submission may have reclaimed the tab while this drive was waiting
	// for the per-tab admission gate. Revalidate ownership only after the gate is
	// held, immediately before attaching the route-specific forwarder.
	if a.botBridge == nil || a.botBridge.TakeoverTab(route) != tabID {
		return fmt.Errorf("接管已解除，请重新接管会话")
	}
	target := botForwardTarget{
		ConnID:   route.ConnectionID,
		Domain:   route.Domain,
		ChatID:   route.ChatID,
		ChatType: route.ChatType,
	}
	generation := tab.sink.SetBotSink(newBotEventForwarder(a.botRuntime, []botForwardTarget{target}))
	a.ensureTabTopicIndexedForUserTurn(tab)
	ctrl.SubmitDisplay(text, text)
	// Confirm the submit actually started a turn. If nothing is running now, the
	// controller was rotating and the submit no-oped — detach this exact
	// generation so a later turn's output does not leak.
	if !admission.finish(ctrl) {
		tab.sink.clearBotSink(generation)
		return errDriveBusy
	}
	return nil
}

// bridgePersistWatchers 把订阅全集回写用户配置（bot.desktop_watchers），
// 桌面重启后由 refreshBotRuntime 重新种子。
func (a *App) bridgePersistWatchers(routes []bot.DesktopWatchRoute) error {
	return a.applyConfigOnly(func(c *config.Config) error {
		watchers := make([]config.BotDesktopWatcherConfig, 0, len(routes))
		for _, r := range routes {
			watchers = append(watchers, config.BotDesktopWatcherConfig{
				Platform:     string(r.Platform),
				ConnectionID: r.ConnectionID,
				Domain:       r.Domain,
				ChatType:     string(r.ChatType),
				ChatID:       r.ChatID,
			})
		}
		c.Bot.DesktopWatchers = watchers
		return nil
	})
}

func bridgeRoutesFromConfig(watchers []config.BotDesktopWatcherConfig) []bot.DesktopWatchRoute {
	routes := make([]bot.DesktopWatchRoute, 0, len(watchers))
	for _, w := range watchers {
		routes = append(routes, bot.DesktopWatchRoute{
			Platform:     bot.Platform(strings.TrimSpace(w.Platform)),
			ConnectionID: strings.TrimSpace(w.ConnectionID),
			Domain:       strings.TrimSpace(w.Domain),
			ChatType:     bot.ChatType(strings.TrimSpace(w.ChatType)),
			ChatID:       strings.TrimSpace(w.ChatID),
		})
	}
	return routes
}
