/**
 * The Fil (« Aujourd'hui ») — PURE Pinia selectors, ZERO notification table.
 * Everything derives from the collective state (BLUEPRINT-V2.md « Navigation »
 * COUCHES CODE): windows, votes, due reviews, due reports, boundaries,
 * influxes, pending crystallizations, complete dossiers, R6 suggestions.
 *
 * Every section is a computed array sorted by deadline; myCount sums the
 * sections that ask ME for a gesture (the bare counter of « À toi de décider »
 * — never red, never a debt word).
 *
 * EXPLICIT imports (no Nuxt auto-imports) — testable under plain vitest.
 */

import { computed, type ComputedRef } from 'vue'
import type {
  Concern,
  Decision,
  Id,
  Mandate,
  Objection,
  Protocol,
  VoteSession,
} from '../types/domain'
import { TERMINAL_STATUSES } from '../engine'
import { useCollectiveStore } from '../stores/collective'

export interface SessionFeedItem {
  session: VoteSession
  decision: Decision | undefined
}

export interface MandateReportDueItem {
  mandate: Mandate
  dueAt: string
}

export type FeedSuggestion =
  | { kind: 'claim-mandate'; tags: string[]; count: number }
  | { kind: 'protocolize'; tags: string[]; count: number }
  | { kind: 'prune-protocol'; protocol: Protocol }

export interface Feed {
  objectionWindows: ComputedRef<Decision[]>
  adviceRequests: ComputedRef<Decision[]>
  openVotes: ComputedRef<SessionFeedItem[]>
  toCrystallize: ComputedRef<SessionFeedItem[]>
  dossiersComplete: ComputedRef<Decision[]>
  tiesToBreak: ComputedRef<SessionFeedItem[]>
  reviewsDue: ComputedRef<Decision[]>
  mandateReportsDue: ComputedRef<MandateReportDueItem[]>
  boundaryObjections: ComputedRef<Objection[]>
  overflowingScopes: ComputedRef<Decision[]>
  prioritiesAsked: ComputedRef<Concern[]>
  suggestions: ComputedRef<FeedSuggestion[]>
  collectiveActivity: ComputedRef<Decision[]>
  myCount: ComputedRef<number>
}

const byDeadline
  = (deadline: (d: Decision) => string) => (a: Decision, b: Decision) =>
    deadline(a) < deadline(b) ? -1 : 1

export function useFeed(): Feed {
  const col = useCollectiveStore()

  const myId = computed<Id | null>(() => col.me?.id ?? null)

  /** Ids of the decisions where a live Concern names me. */
  const concernedDecisionIds = computed<Set<Id>>(() => {
    const me = myId.value
    const set = new Set<Id>()
    if (!me) return set
    for (const concern of col.concerns) {
      if (concern.personId === me) set.add(concern.decisionId)
    }
    return set
  })

  const isMySteward = (decision: Decision): boolean => {
    const me = myId.value
    if (!me) return false
    if (decision.stewardIds.length > 0) return decision.stewardIds.includes(me)
    return decision.authorId === me
  }

  const decisionOf = (session: VoteSession): Decision | undefined =>
    col.decisions.find(d => d.id === session.decisionId)

  // ── Windows where I am concerned ───────────────────────────
  const objectionWindows = computed(() =>
    col.decisions
      .filter(d => d.status === 'objection' && concernedDecisionIds.value.has(d.id))
      .sort(byDeadline(d => d.windowEndsAt ?? '')),
  )

  const adviceRequests = computed(() =>
    col.decisions
      .filter(d => d.status === 'advice' && concernedDecisionIds.value.has(d.id))
      .sort(byDeadline(d => d.windowEndsAt ?? '')),
  )

  // ── Open votes where I belong to the arrested list ─────────
  const openVotes = computed<SessionFeedItem[]>(() => {
    const me = myId.value
    if (!me) return []
    return col.sessions
      .filter(s => s.status === 'open' && s.corpusPersonIds.includes(me))
      .sort((a, b) => (a.closesAt < b.closesAt ? -1 : 1))
      .map(session => ({ session, decision: decisionOf(session) }))
  })

  // ── Frozen parametric sessions waiting for MY gesture ──────
  const toCrystallize = computed<SessionFeedItem[]>(() =>
    col.sessions
      .filter((s) => {
        if (s.status !== 'frozen') return false
        const decision = decisionOf(s)
        return decision !== undefined && isMySteward(decision)
      })
      .sort((a, b) => (a.closesAt < b.closesAt ? -1 : 1))
      .map(session => ({ session, decision: decisionOf(session) })),
  )

  // ── Dossiers whose element children are all terminal ───────
  const dossiersComplete = computed(() =>
    col.decisions
      .filter((d) => {
        if (d.status !== 'framing' || !isMySteward(d)) return false
        const elements = col.decisions.filter(
          child => child.parentDecisionId === d.id && child.chainKind === 'element',
        )
        return (
          elements.length > 0
          && elements.every(child => TERMINAL_STATUSES.includes(child.status))
        )
      })
      .sort(byDeadline(d => d.windowEndsAt ?? '')),
  )

  // ── Tied elections waiting for a HUMAN runoff ──────────────
  const tiesToBreak = computed<SessionFeedItem[]>(() =>
    col.sessions
      .filter((s) => {
        if (s.outcome !== 'tie') return false
        const decision = decisionOf(s)
        return (
          decision !== undefined && decision.status === 'voting' && isMySteward(decision)
        )
      })
      .sort((a, b) => (a.closesAt < b.closesAt ? -1 : 1))
      .map(session => ({ session, decision: decisionOf(session) })),
  )

  // ── Reviews due — « Le réel a-t-il suivi ? » ───────────────
  const reviewsDue = computed(() => {
    const me = myId.value
    if (!me) return []
    const now = new Date().toISOString()
    return col.decisions
      .filter((d) => {
        if (d.status !== 'adopted' || !d.review || d.review.verdict) return false
        if (d.review.dueAt > now) return false
        return d.authorId === me || d.stewardIds.includes(me) || d.measurerIds.includes(me)
      })
      .sort(byDeadline(d => d.review?.dueAt ?? ''))
  })

  // ── Mandate reports I owe ──────────────────────────────────
  const mandateReportsDue = computed<MandateReportDueItem[]>(() => {
    const me = myId.value
    if (!me) return []
    const now = new Date().toISOString()
    return col.mandates
      .filter(m => m.status === 'active' && m.holderId === me)
      .flatMap((mandate) => {
        const due = mandate.reports
          .filter(r => !r.deliveredAt && r.dueAt <= now)
          .sort((a, b) => (a.dueAt < b.dueAt ? -1 : 1))[0]
        return due ? [{ mandate, dueAt: due.dueAt }] : []
      })
      .sort((a, b) => (a.dueAt < b.dueAt ? -1 : 1))
  })

  // ── Boundary objections on MY decisions (highest priority) ─
  const boundaryObjections = computed(() => {
    const me = myId.value
    if (!me) return []
    const mine = new Set(col.decisions.filter(d => d.authorId === me).map(d => d.id))
    return col.objections
      .filter(o => o.kind === 'boundary' && o.status === 'open' && mine.has(o.decisionId))
      .sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1))
  })

  // ── My perimeters overflowing (widen or motivate) ──────────
  const overflowingScopes = computed(() => {
    const me = myId.value
    const settings = col.settings
    if (!me || !settings) return []
    const ratio = settings.triage.concernEscalateRatio
    return col.decisions
      .filter((d) => {
        if (d.authorId !== me || d.scopeKeptNote) return false
        if (!['objection', 'advice', 'voting'].includes(d.status)) return false
        const concerns = col.concerns.filter(c => c.decisionId === d.id)
        const computedCount = concerns.filter(c => c.origin === 'computed').length
        const declaredCount = concerns.filter(c => c.origin === 'declared').length
        return computedCount > 0 && declaredCount >= ratio * computedCount
      })
      .sort(byDeadline(d => d.windowEndsAt ?? ''))
  })

  // ── « pondère tes enjeux » — my unweighted element concerns ─
  const prioritiesAsked = computed(() => {
    const me = myId.value
    if (!me) return []
    const openElements = new Set(
      col.decisions
        .filter(
          d => d.chainKind === 'element' && !TERMINAL_STATUSES.includes(d.status),
        )
        .map(d => d.id),
    )
    return col.concerns.filter(
      c => c.personId === me && c.priority === undefined && openElements.has(c.decisionId),
    )
  })

  // ── R6 / maturation / pruning — simple, never blocking ─────
  const suggestions = computed<FeedSuggestion[]>(() => {
    const settings = col.settings
    if (!settings) return []
    const threshold = settings.triage.recurrenceThreshold
    const out: FeedSuggestion[] = []

    // Group by shared tag (simple heuristic): a tag carried by ≥ threshold
    // adopted decisions of the last 90 days suggests a mandate; the same on
    // 'record' entries suggests protocolizing the ripe practice.
    const since = new Date(Date.now() - 90 * 86_400_000).toISOString()
    const countByTag = (rows: Decision[]): Map<string, number> => {
      const map = new Map<string, number>()
      for (const row of rows) {
        for (const tag of row.tags) map.set(tag, (map.get(tag) ?? 0) + 1)
      }
      return map
    }
    const recent = col.decisions.filter(d => (d.decidedAt ?? d.createdAt) >= since)
    for (const [tag, count] of countByTag(
      recent.filter(d => d.status === 'adopted' && d.route !== 'record'),
    )) {
      if (count >= threshold) out.push({ kind: 'claim-mandate', tags: [tag], count })
    }
    for (const [tag, count] of countByTag(recent.filter(d => d.route === 'record'))) {
      if (count >= threshold) out.push({ kind: 'protocolize', tags: [tag], count })
    }

    // Pruning: a protocol no session ever invoked.
    const invoked = new Set(col.sessions.map(s => s.protocolId))
    for (const protocol of col.protocols) {
      if (!invoked.has(protocol.id) && protocol.method !== 'consent') {
        out.push({ kind: 'prune-protocol', protocol })
      }
    }
    return out
  })

  // ── The collective's activity (visibility respected) ───────
  const collectiveActivity = computed(() => {
    const me = myId.value
    return col.decisions
      .filter(d => d.visibility !== 'private' || d.authorId === me)
      .sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1))
      .slice(0, 20)
  })

  // ── The bare counter — sections that ask ME for a gesture ──
  const myCount = computed(
    () =>
      objectionWindows.value.length
      + adviceRequests.value.length
      + openVotes.value.length
      + toCrystallize.value.length
      + dossiersComplete.value.length
      + tiesToBreak.value.length
      + reviewsDue.value.length
      + mandateReportsDue.value.length
      + boundaryObjections.value.length
      + overflowingScopes.value.length
      + prioritiesAsked.value.length,
  )

  return {
    objectionWindows,
    adviceRequests,
    openVotes,
    toCrystallize,
    dossiersComplete,
    tiesToBreak,
    reviewsDue,
    mandateReportsDue,
    boundaryObjections,
    overflowingScopes,
    prioritiesAsked,
    suggestions,
    collectiveActivity,
    myCount,
  }
}
