/**
 * Auto-Participants — "Automatically Include People" (Nash, 2026-09-30;
 * docs/TASKS-2026-09-30-AUTO-PARTICIPANTS.md). Pure rules; the I/O lives in
 * src/lib/data/auto-participants.ts.
 *
 * A Personal Auto-Participant follows one person: "whenever I join or create
 * a Trip, include Maria." A Company Auto-Participant follows every approved
 * member of a company: "whenever someone from CP Star takes part in a Trip,
 * include the Safety Manager." Either way the result is an ordinary
 * trip_participants row — Auto-Participant is how someone got on, never a
 * role (§16).
 */
import type { TripRole } from '@/types/db'
import type {
  AdditionMethod, AutoParticipantRule, AutoParticipantRuleStatus, AutoParticipantScope, TripParticipant,
} from '@/types/db'
import { CARRIER_DISPATCH_ROLES, ROLE_LABELS, ROLE_TYPES, isCarrierDispatchRole, type RoleType } from '@/lib/domain/modes'

/* -------------------------------------------------------------- words */

export const FEATURE_NAME = 'Auto-Participants'
export const FEATURE_TAGLINE = 'Automatically Include People'
export const FEATURE_EXPLANATION =
  'Auto-Participants are people who are automatically added to applicable Trips so you do not have to invite them manually each time.'

export const RULE_STATUS_LABELS: Record<AutoParticipantRuleStatus, string> = {
  pending: 'Pending Acceptance',
  active: 'Active',
  declined: 'Declined',
  revoked: 'Removed',
  disabled: 'Disabled',
  suspended: 'Suspended',
}

export const SCOPE_LABELS: Record<AutoParticipantScope, string> = {
  all_personal_trips: 'All my Trips',
  all_company_trips: 'All Company Trips',
  carrier_trips: 'Carrier Trips',
  freight_broker_trips: 'Freight Broker Trips',
  pilot_company_trips: 'Pilot Company Trips',
}

export const COMPANY_SCOPES: AutoParticipantScope[] = ['all_company_trips', 'carrier_trips', 'freight_broker_trips', 'pilot_company_trips']

export function ruleStatusLabel(status: AutoParticipantRuleStatus): string {
  return RULE_STATUS_LABELS[status] ?? status
}

export function scopeLabel(scope: AutoParticipantScope): string {
  return SCOPE_LABELS[scope] ?? scope
}

/** §20: removing a rule never touches trips the person is already on. */
export const RULE_REMOVAL_NOTE =
  'This person will no longer be added automatically to future Trips. Existing Trip participation remains unchanged.'

/* --------------------------------------------------------- transitions */

export const RULE_TRANSITIONS: Record<AutoParticipantRuleStatus, AutoParticipantRuleStatus[]> = {
  pending: ['active', 'declined', 'revoked'],
  active: ['disabled', 'revoked', 'suspended'],
  disabled: ['active', 'revoked'],
  // A suspended company rule comes back only once the membership is approved again (Task 10.1).
  suspended: ['active', 'revoked'],
  declined: [],
  revoked: [],
}

export function canTransition(from: AutoParticipantRuleStatus, to: AutoParticipantRuleStatus): boolean {
  return RULE_TRANSITIONS[from]?.includes(to) ?? false
}

/** Statuses a live rule may hold (counted by the uniqueness indexes, §33). */
export const LIVE_RULE_STATUSES: AutoParticipantRuleStatus[] = ['pending', 'active', 'disabled', 'suspended']

/* ---------------------------------------------------------------- roles */

/**
 * Decision D1: an auto-added person keeps THEIR OWN role. This maps that role
 * to the trip role column; it is the inverse of ROLE_CONTEXT.
 */
export function tripRoleForRoleType(roleType: string | null | undefined): TripRole | null {
  if (!roleType) return null
  if (isCarrierDispatchRole(roleType)) return 'dispatcher'
  switch (roleType) {
    case 'carrier_driver': return 'driver'
    case 'freight_broker': return 'broker'
    case 'pilot_company_dispatch':
    case 'pilot_driver': return 'pilot'
    default: return null
  }
}

/** Which of a person's roles may stand behind a trip role (the chooser, D13). */
export function roleTypesMatchingTripRole(tripRole: TripRole | string): RoleType[] {
  return ROLE_TYPES.filter((r) => tripRoleForRoleType(r) === tripRole)
}

export function roleTypeLabel(roleType: string | null | undefined): string {
  return roleType && (ROLE_TYPES as string[]).includes(roleType) ? ROLE_LABELS[roleType as RoleType] : roleType ?? ''
}

/* -------------------------------------------------------------- scopes */

/**
 * Decision D2 + D13: company rules follow the company's people, evaluated for
 * the ONE company the triggering participant acts for on this trip. The scope
 * narrows by the role they act with there.
 */
export function companyScopeMatches(scope: AutoParticipantScope, contextRoleType: string | null | undefined): boolean {
  if (!contextRoleType) return false
  switch (scope) {
    case 'all_company_trips':
      return (ROLE_TYPES as string[]).includes(contextRoleType)
    case 'carrier_trips':
      return contextRoleType === 'carrier_driver' || (CARRIER_DISPATCH_ROLES as string[]).includes(contextRoleType)
    case 'freight_broker_trips':
      return contextRoleType === 'freight_broker'
    case 'pilot_company_trips':
      return contextRoleType === 'pilot_company_dispatch' || contextRoleType === 'pilot_driver'
    default:
      return false
  }
}

/* ------------------------------------------------------------ provenance */

export const AUTO_ADDITION_METHODS: AdditionMethod[] = ['personal_auto_participant', 'company_auto_participant']

export function isAutoAdded(p: Pick<TripParticipant, 'addition_method'> | null | undefined): boolean {
  return !!p?.addition_method && (AUTO_ADDITION_METHODS as string[]).includes(p.addition_method)
}

/** §26: informational line under the role badge; never styled like a role. */
export function provenanceLine(
  p: Pick<TripParticipant, 'addition_method'> & { triggering_user_name?: string | null; triggering_company_name?: string | null },
): string | null {
  if (p.addition_method === 'personal_auto_participant') return `Auto-added via ${p.triggering_user_name || 'an Auto-Participant rule'}`
  if (p.addition_method === 'company_auto_participant') return `Auto-added by ${p.triggering_company_name || 'company policy'}`
  return null
}

/* ----------------------------------------------------------- the decision */

export type AutoAddSkipReason =
  | 'rule_not_active'
  | 'recursive'
  | 'admin_preview'
  | 'before_effective_from'
  | 'context_pending'
  | 'participant_unresolved'
  | 'participant_blocked'
  | 'participant_not_claimed'
  | 'membership_lost'
  | 'already_on_trip'
  | 'explicitly_removed'
  | 'imported_row_exists'
  | 'self'

export type AutoAddDecision = { add: true } | { add: false; reason: AutoAddSkipReason }

export interface AutoAddInput {
  rule: Pick<AutoParticipantRule, 'status' | 'effective_from' | 'participant_user_id' | 'rule_type'>
  trigger: {
    /** The row whose placement on the trip fired the rule. */
    participant: Pick<TripParticipant, 'user_id' | 'addition_method'>
    /** Why the resolver is running. */
    kind: 'participation' | 'claim' | 'synchron_import' | 'context_chosen'
    /** When the qualifying participation happened (now) — or, for a claim, the trip's import date (D10). */
    at: string
    /** The session behind the action, when there is one. */
    session?: { id: string; originId: string; preview?: string | null } | null
  }
  participant: {
    /** The person the rule names, once resolved. */
    userId: string | null
    blockedAt?: string | null
    claimStatus?: string | null
    hasAccount?: boolean
    /** Company rules: the person still holds an approved membership at the company, or was accepted as external. */
    membershipOk?: boolean
    /** Their existing row on the trip, any status. */
    existingStatus?: string | null
  }
}

/**
 * The gates, in the order the resolver applies them (Task 4.4). Pure, so the
 * reasons can be tested one by one. `already_on_trip` is not a failure: the
 * resolver records the extra reason instead of a second row (§18).
 */
export function autoAddDecision(input: AutoAddInput): AutoAddDecision {
  const { rule, trigger, participant } = input
  if (rule.status !== 'active') return { add: false, reason: 'rule_not_active' }
  if (isAutoAdded(trigger.participant)) return { add: false, reason: 'recursive' }
  if (trigger.session && (trigger.session.originId !== trigger.session.id || trigger.session.preview)) return { add: false, reason: 'admin_preview' }
  if (rule.effective_from && trigger.at < rule.effective_from) return { add: false, reason: 'before_effective_from' }
  if (!participant.userId || !rule.participant_user_id) return { add: false, reason: 'participant_unresolved' }
  if (participant.userId === trigger.participant.user_id) return { add: false, reason: 'self' }
  if (participant.blockedAt) return { add: false, reason: 'participant_blocked' }
  if (participant.hasAccount === false || isUnclaimed(participant.claimStatus)) return { add: false, reason: 'participant_not_claimed' }
  if (rule.rule_type === 'company' && participant.membershipOk === false) return { add: false, reason: 'membership_lost' }
  if (participant.existingStatus === 'invited' || participant.existingStatus === 'active') return { add: false, reason: 'already_on_trip' }
  if (participant.existingStatus === 'removed') return { add: false, reason: 'explicitly_removed' }
  if (participant.existingStatus === 'imported') return { add: false, reason: 'imported_row_exists' }
  return { add: true }
}

/** §31: an imported, unclaimed person is not a user yet. */
export function isUnclaimed(claimStatus: string | null | undefined): boolean {
  return claimStatus === 'unclaimed' || claimStatus === 'claim_candidate' || claimStatus === 'manual_review' || claimStatus === 'conflict'
}

/* --------------------------------------------------------------- email */

export function normalizeEmail(email: string): string {
  return email.trim().toLowerCase()
}

/* ---------------------------------------------------------- the chooser */

/**
 * Decision D13: a person with several contexts matching the trip role they
 * were placed with chooses one per trip. With exactly one, it is stored
 * silently; with none, there is nothing to choose (a personal rule still
 * fires; company rules wait).
 */
export function contextChoice<T extends { roleType: RoleType; companyId: string | null; status: string }>(
  contexts: T[],
  tripRole: TripRole | string,
  /** Company rules: restrict to this company (A4 — role chosen among their roles there). */
  companyId?: string | null,
): { kind: 'one'; context: T } | { kind: 'choose'; contexts: T[] } | { kind: 'none' } {
  const matching = contexts.filter((c) => c.status === 'active' && (companyId ? c.companyId === companyId : tripRoleForRoleType(c.roleType) === tripRole))
  if (matching.length === 0) return { kind: 'none' }
  if (matching.length === 1) return { kind: 'one', context: matching[0] }
  return { kind: 'choose', contexts: matching }
}

/**
 * A11: a company-rule member whose roles at the company map to different trip
 * roles is inserted with the dispatcher-power role first; the chooser may
 * switch the trip role later.
 */
export function initialTripRoleForCompanyMember(roleTypesAtCompany: string[]): TripRole | null {
  const mapped = roleTypesAtCompany.map(tripRoleForRoleType).filter((r): r is TripRole => !!r)
  if (mapped.length === 0) return null
  if (mapped.includes('dispatcher')) return 'dispatcher'
  return mapped[0]
}
