import APIClient
import SwiftUI

/// T-iOS-26 · Projects 列表屏（镜像 web v0.6 projects 面板）：namespace 分组
/// 折叠分区 + 收藏星标 + dirty 徽标 + Active now 置顶 + 搜索。纯呈现 ——
/// 分组/收藏/折叠/导航信号全部是 `ProjectsViewModel` 逻辑（单测覆盖）。
///
/// 安全：项目名/路径/分支是**不可信服务器字节** —— 一律 `Text(verbatim:)`
/// （绝不 LocalizedStringKey/Markdown），单行截断。
struct ProjectsScreen: View {
    @Bindable var viewModel: ProjectsViewModel
    /// "在此仓库开新会话" 导航钩子（AppCoordinator.openProject 消费）。
    var onOpen: (ProjectOpenRequest) -> Void = { _ in }
    /// T-iPad-4 · 设备 idiom 只在此读一次，交给 `ProjectsGridLayout`/
    /// `ProjectsSheetSizing` 决策（视图里零散落条件）。iPhone → 现有单列 List
    /// （字节级不变）；iPad → 多列网格 + 卡片式 sheet detents。idiom 是设备常量、
    /// 运行期不变，故非 @Environment 即可。见 `ProjectsLayout` 注释解释为何用
    /// idiom 而非 size class（iPad 表单 sheet 内部恒 compact）。
    private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }

    private enum Copy {
        static let emptyNoProjectsTitle = "暂无项目"
        static let emptyNoMatchTitle = "无匹配项目"
    }

    var body: some View {
        adaptiveContent
            .navigationTitle(ProjectsCopy.title)
            .navigationBarTitleDisplayMode(.inline)
            .searchable(text: $viewModel.searchText, prompt: ProjectsCopy.searchPrompt)
            .refreshable { await viewModel.refresh() }
            .task { await viewModel.load() }
            .onChange(of: viewModel.openRequest) { _, request in
                guard let request else { return }
                onOpen(request)
            }
            .navigationDestination(for: ProjectRoute.self) { route in
                ProjectDetailScreen(
                    viewModel: viewModel.makeDetailViewModel(path: route.path),
                    endpoint: viewModel.host.endpoint,
                    http: viewModel.http,
                    onOpenClaude: { viewModel.requestOpenClaude(cwd: $0) }
                )
            }
            // T-iPad-4 · iPhone 透传（现有全高 sheet 字节级不变）；iPad 套
            // 卡片式 detents（不铺满大屏）。
            .adaptiveProjectsSheetDetents(idiom: idiom)
    }

    // MARK: - 自适应容器（唯一 idiom 消费点，经 ProjectsGridLayout）

    /// iPhone → 现有单列 `list`（原样复用，字节级零回归）；iPad → 多列
    /// `gridList`。分组/折叠/收藏/prefs 往返全部是同一 `ProjectsViewModel`
    /// 逻辑，两条路径只换视觉容器。
    @ViewBuilder private var adaptiveContent: some View {
        if ProjectsGridLayout.usesGrid(idiom: idiom) {
            gridList
        } else {
            list
        }
    }

    // MARK: - List

    private var list: some View {
        List {
            errorRows
            if let message = viewModel.emptyStateMessage {
                emptyState(message)
                    .frame(maxWidth: .infinity)
                    .listRowSeparator(.hidden)
                    .listRowBackground(Color.clear)
            }
            ForEach(viewModel.groups) { group in
                groupSection(group)
            }
        }
        .listStyle(.insetGrouped)
        .overlay {
            if !viewModel.hasLoadedOnce && viewModel.fetchErrorMessage == nil {
                ProgressView()
            }
        }
    }

    /// 空态：区分「无项目」与「无搜索结果」（symbol + 文案；friendly，非错误）。
    private func emptyState(_ message: String) -> some View {
        ContentUnavailableView {
            Label(
                viewModel.isSearching ? Copy.emptyNoMatchTitle : Copy.emptyNoProjectsTitle,
                systemImage: viewModel.isSearching ? "magnifyingglass" : "folder"
            )
        } description: {
            Text(message)
        }
    }

    // MARK: - Grid（regular 宽度：多列网格）

    /// iPad 分栏/大屏下的多列网格。列数由 `ProjectsGridLayout.columnCount` 按
    /// **可用宽度**（GeometryReader）决定；分组头/折叠/收藏/行渲染全部复用 List
    /// 路径的同名 builder（`groupHeader`/`projectRow`/`errorRows`）—— 只换外层
    /// 容器，分组/收藏/prefs 往返逻辑零改。
    private var gridList: some View {
        GeometryReader { proxy in
            let columns = ProjectsGridLayout.columnCount(
                availableWidth: proxy.size.width,
                idiom: idiom
            )
            ScrollView {
                LazyVStack(alignment: .leading, spacing: DS.Space.md12) {
                    errorRows
                    if let message = viewModel.emptyStateMessage {
                        emptyState(message)
                            .frame(maxWidth: .infinity)
                    }
                    ForEach(viewModel.groups) { group in
                        gridSection(group, columns: columns)
                    }
                }
                .padding(DS.Space.lg16)
            }
            .overlay {
                if !viewModel.hasLoadedOnce && viewModel.fetchErrorMessage == nil {
                    ProgressView()
                }
            }
        }
    }

    @ViewBuilder private func gridSection(_ group: ProjectGroup, columns: Int) -> some View {
        let isCollapsed = viewModel.isCollapsed(group)
        VStack(alignment: .leading, spacing: DS.Space.sm8) {
            if group.kind != .flat {
                groupHeader(group, isCollapsed: isCollapsed)
                    .padding(.top, DS.Space.md12)
            }
            if !isCollapsed {
                LazyVGrid(
                    columns: gridColumns(columns),
                    alignment: .leading,
                    spacing: DS.Space.sm8
                ) {
                    ForEach(group.projects, id: \.path) { project in
                        // 卡片 = DS Card（secondary 底 + 发丝描边 + md12）。
                        Card(padding: DS.Space.md12) {
                            projectRow(project, group: group)
                        }
                    }
                }
            }
        }
    }

    /// 等宽弹性列（`columns >= 1` 由 `ProjectsGridLayout` 保证，绝不空网格）。
    private func gridColumns(_ count: Int) -> [GridItem] {
        Array(
            repeating: GridItem(.flexible(), spacing: DS.Space.sm8),
            count: max(count, ProjectsGridLayout.singleColumn)
        )
    }

    /// 显式错误行（刷新失败留旧列表、prefs 失败降级本地 —— 都要可见）。
    @ViewBuilder private var errorRows: some View {
        ForEach(
            [
                viewModel.fetchErrorMessage,
                viewModel.prefsErrorMessage,
                viewModel.prefsSyncErrorMessage,
                viewModel.openErrorMessage,
            ].compactMap(\.self),
            id: \.self
        ) { message in
            Label(message, systemImage: "exclamationmark.triangle")
                .font(DS.Typography.caption)
                .foregroundStyle(DS.Palette.statusWaiting)
                .listRowSeparator(.hidden)
        }
    }

    // MARK: - Group section（flat 无 chrome；namespace/other 可折叠）

    @ViewBuilder private func groupSection(_ group: ProjectGroup) -> some View {
        let isCollapsed = viewModel.isCollapsed(group)
        Section {
            if !isCollapsed {
                ForEach(group.projects, id: \.path) { project in
                    projectRow(project, group: group)
                }
            }
        } header: {
            if group.kind != .flat {
                groupHeader(group, isCollapsed: isCollapsed)
            }
        }
    }

    private func groupHeader(_ group: ProjectGroup, isCollapsed: Bool) -> some View {
        HStack(spacing: DS.Space.sm8) {
            if group.isCollapsible {
                Image(systemName: isCollapsed ? "chevron.right" : "chevron.down")
                    .font(DS.Typography.caption)
                    .foregroundStyle(DS.Palette.textSecondary)
            }
            // namespace label 来自服务器仓库名 → verbatim。
            Text(verbatim: group.label)
                .font(DS.Typography.caption)
                .foregroundStyle(DS.Palette.textSecondary)
                .lineLimit(1)
            // 计数含数字 → 等宽 tabular。
            Text(verbatim: "\(group.projects.count)")
                .font(DS.Typography.metaMono)
                .foregroundStyle(DS.Palette.textTertiary)
            // 折叠的分区绝不悄悄埋掉活跃会话（镜像 web 组头徽标）。
            if group.kind != .active && group.activeCount > 0 {
                Text(ProjectsCopy.activeCountBadge(group.activeCount))
                    .font(DS.Typography.metaMono)
                    .foregroundStyle(DS.Palette.statusWorking)
            }
            Spacer(minLength: 0)
        }
        .contentShape(Rectangle())
        .onTapGesture {
            guard group.isCollapsible else { return }
            Task { await viewModel.toggleCollapsed(key: group.key) }
        }
        .accessibilityAddTraits(group.isCollapsible ? .isButton : [])
    }

    // MARK: - Project row

    private func projectRow(_ project: ProjectInfo, group: ProjectGroup) -> some View {
        NavigationLink(value: ProjectRoute(path: project.path)) {
            HStack(spacing: DS.Space.sm8) {
                favouriteButton(project)
                VStack(alignment: .leading, spacing: DS.Space.xs2) {
                    Text(verbatim: ProjectGrouping.displayLabel(
                        name: project.name, groupKey: group.key
                    ))
                    .font(DS.Typography.body.weight(.medium))
                    .foregroundStyle(DS.Palette.textPrimary)
                    .lineLimit(1)
                    projectChips(project)
                }
                Spacer(minLength: 0)
                // 有运行中会话 → DS 状态徽标（色 + 形 + VoiceOver，非仅靠颜色）。
                if ProjectGrouping.hasRunningSession(project) {
                    StatusBadge(status: .working)
                }
            }
        }
    }

    private func favouriteButton(_ project: ProjectInfo) -> some View {
        Button {
            DS.Haptics.selection()
            Task { await viewModel.toggleFavourite(path: project.path) }
        } label: {
            let isFav = viewModel.isFavourite(project.path)
            Image(systemName: isFav ? "star.fill" : "star")
                .foregroundStyle(isFav ? DS.Palette.accent : DS.Palette.textTertiary)
        }
        .buttonStyle(.borderless) // List 行内独立可点
    }

    @ViewBuilder private func projectChips(_ project: ProjectInfo) -> some View {
        HStack(spacing: DS.Space.sm8) {
            if let branch = project.branch {
                Label {
                    Text(verbatim: branch).lineLimit(1)
                } icon: {
                    Image(systemName: "arrow.triangle.branch")
                }
                .font(DS.Typography.caption)
                .foregroundStyle(DS.Palette.textSecondary)
            }
            if project.dirty == true {
                DirtyBadge()
            }
        }
    }
}

/// 详情屏的 push 路由值（value-based navigation）。
struct ProjectRoute: Hashable {
    let path: String
}
